2015-08-03 01:47:31 +02:00
using MediaBrowser.Common ;
2015-07-20 20:32:55 +02:00
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Common.Net ;
2015-08-21 21:25:35 +02:00
using MediaBrowser.Common.Security ;
2015-08-22 21:46:55 +02:00
using MediaBrowser.Controller.Configuration ;
2015-07-20 20:32:55 +02:00
using MediaBrowser.Controller.Drawing ;
2015-08-22 21:46:55 +02:00
using MediaBrowser.Controller.FileOrganization ;
using MediaBrowser.Controller.Library ;
2015-07-20 20:32:55 +02:00
using MediaBrowser.Controller.LiveTv ;
2015-09-21 18:26:05 +02:00
using MediaBrowser.Controller.MediaEncoding ;
2015-08-22 21:46:55 +02:00
using MediaBrowser.Controller.Providers ;
2015-07-20 20:32:55 +02:00
using MediaBrowser.Model.Dto ;
2015-08-21 21:25:35 +02:00
using MediaBrowser.Model.Entities ;
2015-07-20 20:32:55 +02:00
using MediaBrowser.Model.Events ;
2015-08-22 21:46:55 +02:00
using MediaBrowser.Model.FileOrganization ;
2015-07-20 20:32:55 +02:00
using MediaBrowser.Model.LiveTv ;
using MediaBrowser.Model.Logging ;
using MediaBrowser.Model.Serialization ;
2015-08-22 21:46:55 +02:00
using MediaBrowser.Server.Implementations.FileOrganization ;
2015-07-20 20:32:55 +02:00
using System ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
2016-01-26 19:18:54 +01:00
using System.Globalization ;
2015-07-20 20:32:55 +02:00
using System.IO ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
2015-10-04 06:23:11 +02:00
using CommonIO ;
2015-10-01 18:28:24 +02:00
using MediaBrowser.Common.Extensions ;
2016-01-21 18:29:14 +01:00
using MediaBrowser.Controller.Power ;
2016-01-27 07:07:01 +01:00
using Microsoft.Win32 ;
2015-07-20 20:32:55 +02:00
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
2015-08-22 21:46:55 +02:00
public class EmbyTV : ILiveTvService , IHasRegistrationInfo , IDisposable
2015-07-20 20:32:55 +02:00
{
2015-07-21 06:22:46 +02:00
private readonly IApplicationHost _appHpst ;
2015-07-20 20:32:55 +02:00
private readonly ILogger _logger ;
private readonly IHttpClient _httpClient ;
2015-08-22 21:46:55 +02:00
private readonly IServerConfigurationManager _config ;
2015-07-20 20:32:55 +02:00
private readonly IJsonSerializer _jsonSerializer ;
private readonly ItemDataProvider < RecordingInfo > _recordingProvider ;
private readonly ItemDataProvider < SeriesTimerInfo > _seriesTimerProvider ;
private readonly TimerManager _timerProvider ;
2015-07-23 15:23:22 +02:00
private readonly LiveTvManager _liveTvManager ;
2015-08-16 20:54:25 +02:00
private readonly IFileSystem _fileSystem ;
2015-08-21 21:25:35 +02:00
private readonly ISecurityManager _security ;
2015-07-23 07:25:55 +02:00
2015-08-22 21:46:55 +02:00
private readonly ILibraryMonitor _libraryMonitor ;
private readonly ILibraryManager _libraryManager ;
private readonly IProviderManager _providerManager ;
private readonly IFileOrganizationService _organizationService ;
2015-09-21 18:26:05 +02:00
private readonly IMediaEncoder _mediaEncoder ;
2015-08-22 21:46:55 +02:00
2015-07-29 05:42:03 +02:00
public static EmbyTV Current ;
2016-01-21 18:29:14 +01:00
public EmbyTV ( IApplicationHost appHost , ILogger logger , IJsonSerializer jsonSerializer , IHttpClient httpClient , IServerConfigurationManager config , ILiveTvManager liveTvManager , IFileSystem fileSystem , ISecurityManager security , ILibraryManager libraryManager , ILibraryMonitor libraryMonitor , IProviderManager providerManager , IFileOrganizationService organizationService , IMediaEncoder mediaEncoder , IPowerManagement powerManagement )
2015-07-20 20:32:55 +02:00
{
2015-07-29 05:42:03 +02:00
Current = this ;
2015-07-21 06:22:46 +02:00
_appHpst = appHost ;
2015-07-20 20:32:55 +02:00
_logger = logger ;
_httpClient = httpClient ;
_config = config ;
2015-08-16 20:54:25 +02:00
_fileSystem = fileSystem ;
2015-08-21 21:25:35 +02:00
_security = security ;
2015-08-22 21:46:55 +02:00
_libraryManager = libraryManager ;
_libraryMonitor = libraryMonitor ;
_providerManager = providerManager ;
_organizationService = organizationService ;
2015-09-21 18:26:05 +02:00
_mediaEncoder = mediaEncoder ;
2015-07-23 15:23:22 +02:00
_liveTvManager = ( LiveTvManager ) liveTvManager ;
2015-07-20 20:32:55 +02:00
_jsonSerializer = jsonSerializer ;
2015-09-21 18:26:05 +02:00
_recordingProvider = new ItemDataProvider < RecordingInfo > ( fileSystem , jsonSerializer , _logger , Path . Combine ( DataPath , "recordings" ) , ( r1 , r2 ) = > string . Equals ( r1 . Id , r2 . Id , StringComparison . OrdinalIgnoreCase ) ) ;
_seriesTimerProvider = new SeriesTimerManager ( fileSystem , jsonSerializer , _logger , Path . Combine ( DataPath , "seriestimers" ) ) ;
2016-01-21 18:29:14 +01:00
_timerProvider = new TimerManager ( fileSystem , jsonSerializer , _logger , Path . Combine ( DataPath , "timers" ) , powerManagement , _logger ) ;
2015-07-20 20:32:55 +02:00
_timerProvider . TimerFired + = _timerProvider_TimerFired ;
}
2015-07-29 05:42:03 +02:00
public void Start ( )
{
_timerProvider . RestartTimers ( ) ;
2016-01-27 07:07:01 +01:00
SystemEvents . PowerModeChanged + = SystemEvents_PowerModeChanged ;
}
void SystemEvents_PowerModeChanged ( object sender , PowerModeChangedEventArgs e )
{
2016-01-27 17:58:08 +01:00
_logger . Info ( "Power mode changed to {0}" , e . Mode ) ;
2016-01-27 07:07:01 +01:00
if ( e . Mode = = PowerModes . Resume )
{
_timerProvider . RestartTimers ( ) ;
}
2015-07-29 05:42:03 +02:00
}
2015-07-20 20:32:55 +02:00
public event EventHandler DataSourceChanged ;
public event EventHandler < RecordingStatusChangedEventArgs > RecordingStatusChanged ;
2016-03-01 05:24:42 +01:00
private readonly ConcurrentDictionary < string , ActiveRecordingInfo > _activeRecordings =
new ConcurrentDictionary < string , ActiveRecordingInfo > ( StringComparer . OrdinalIgnoreCase ) ;
2015-07-20 20:32:55 +02:00
public string Name
{
get { return "Emby" ; }
}
public string DataPath
{
get { return Path . Combine ( _config . CommonApplicationPaths . DataPath , "livetv" ) ; }
}
public string HomePageUrl
{
get { return "http://emby.media" ; }
}
public async Task < LiveTvServiceStatusInfo > GetStatusInfoAsync ( CancellationToken cancellationToken )
{
var status = new LiveTvServiceStatusInfo ( ) ;
var list = new List < LiveTvTunerInfo > ( ) ;
2015-08-19 18:43:23 +02:00
foreach ( var hostInstance in _liveTvManager . TunerHosts )
2015-07-20 20:32:55 +02:00
{
2015-07-24 01:40:54 +02:00
try
2015-07-20 20:32:55 +02:00
{
2015-08-19 18:43:23 +02:00
var tuners = await hostInstance . GetTunerInfos ( cancellationToken ) . ConfigureAwait ( false ) ;
2015-07-20 20:32:55 +02:00
2015-07-24 01:40:54 +02:00
list . AddRange ( tuners ) ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error getting tuners" , ex ) ;
2015-07-20 20:32:55 +02:00
}
}
status . Tuners = list ;
status . Status = LiveTvServiceStatus . Ok ;
2015-07-21 06:22:46 +02:00
status . Version = _appHpst . ApplicationVersion . ToString ( ) ;
status . IsVisible = false ;
2015-07-20 20:32:55 +02:00
return status ;
}
2015-09-21 18:26:05 +02:00
public async Task RefreshSeriesTimers ( CancellationToken cancellationToken , IProgress < double > progress )
{
2015-11-21 05:34:55 +01:00
var seriesTimers = await GetSeriesTimersAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
2015-09-21 18:26:05 +02:00
List < ChannelInfo > channels = null ;
2015-11-21 05:34:55 +01:00
foreach ( var timer in seriesTimers )
2015-09-21 18:26:05 +02:00
{
List < ProgramInfo > epgData ;
if ( timer . RecordAnyChannel )
{
if ( channels = = null )
{
channels = ( await GetChannelsAsync ( true , CancellationToken . None ) . ConfigureAwait ( false ) ) . ToList ( ) ;
}
var channelIds = channels . Select ( i = > i . Id ) . ToList ( ) ;
epgData = GetEpgDataForChannels ( channelIds ) ;
}
else
{
epgData = GetEpgDataForChannel ( timer . ChannelId ) ;
}
2016-02-07 20:19:41 +01:00
await UpdateTimersForSeriesTimer ( epgData , timer , true ) . ConfigureAwait ( false ) ;
2015-09-21 18:26:05 +02:00
}
2015-11-21 05:34:55 +01:00
var timers = await GetTimersAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
foreach ( var timer in timers . ToList ( ) )
{
if ( DateTime . UtcNow > timer . EndDate & & ! _activeRecordings . ContainsKey ( timer . Id ) )
{
_timerProvider . Delete ( timer ) ;
}
}
2015-09-21 18:26:05 +02:00
}
2015-08-16 20:54:25 +02:00
private List < ChannelInfo > _channelCache = null ;
private async Task < IEnumerable < ChannelInfo > > GetChannelsAsync ( bool enableCache , CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
2015-08-16 20:54:25 +02:00
if ( enableCache & & _channelCache ! = null )
{
return _channelCache . ToList ( ) ;
}
2015-07-20 20:32:55 +02:00
var list = new List < ChannelInfo > ( ) ;
2015-08-19 18:43:23 +02:00
foreach ( var hostInstance in _liveTvManager . TunerHosts )
2015-07-20 20:32:55 +02:00
{
2015-07-24 01:40:54 +02:00
try
2015-07-20 20:32:55 +02:00
{
2015-08-19 18:43:23 +02:00
var channels = await hostInstance . GetChannels ( cancellationToken ) . ConfigureAwait ( false ) ;
2015-07-20 20:32:55 +02:00
2015-08-16 20:37:53 +02:00
list . AddRange ( channels ) ;
2015-07-24 01:40:54 +02:00
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error getting channels" , ex ) ;
2015-07-20 20:32:55 +02:00
}
}
2016-02-24 20:06:26 +01:00
foreach ( var provider in GetListingProviders ( ) )
2015-07-23 07:25:55 +02:00
{
2016-02-24 20:06:26 +01:00
var enabledChannels = list
. Where ( i = > IsListingProviderEnabledForTuner ( provider . Item2 , i . TunerHostId ) )
. ToList ( ) ;
if ( enabledChannels . Count > 0 )
2015-07-23 07:25:55 +02:00
{
2015-08-03 01:47:31 +02:00
try
{
2016-03-16 22:30:49 +01:00
await provider . Item1 . AddMetadata ( provider . Item2 , enabledChannels , cancellationToken ) . ConfigureAwait ( false ) ;
2015-08-03 01:47:31 +02:00
}
catch ( NotSupportedException )
{
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error adding metadata" , ex ) ;
}
2015-07-23 07:25:55 +02:00
}
}
2016-02-24 20:06:26 +01:00
2016-03-17 20:29:53 +01:00
_channelCache = list . ToList ( ) ;
2015-07-20 20:32:55 +02:00
return list ;
}
2015-08-16 20:54:25 +02:00
public Task < IEnumerable < ChannelInfo > > GetChannelsAsync ( CancellationToken cancellationToken )
{
return GetChannelsAsync ( false , cancellationToken ) ;
}
2015-07-20 20:32:55 +02:00
public Task CancelSeriesTimerAsync ( string timerId , CancellationToken cancellationToken )
{
2016-02-01 01:57:40 +01:00
var timers = _timerProvider
. GetAll ( )
. Where ( i = > string . Equals ( i . SeriesTimerId , timerId , StringComparison . OrdinalIgnoreCase ) )
. ToList ( ) ;
2015-08-17 18:52:56 +02:00
foreach ( var timer in timers )
{
CancelTimerInternal ( timer . Id ) ;
}
2015-07-29 05:42:03 +02:00
var remove = _seriesTimerProvider . GetAll ( ) . FirstOrDefault ( r = > string . Equals ( r . Id , timerId , StringComparison . OrdinalIgnoreCase ) ) ;
2015-07-20 20:32:55 +02:00
if ( remove ! = null )
{
_seriesTimerProvider . Delete ( remove ) ;
}
return Task . FromResult ( true ) ;
}
private void CancelTimerInternal ( string timerId )
{
2015-07-29 05:42:03 +02:00
var remove = _timerProvider . GetAll ( ) . FirstOrDefault ( r = > string . Equals ( r . Id , timerId , StringComparison . OrdinalIgnoreCase ) ) ;
2015-07-20 20:32:55 +02:00
if ( remove ! = null )
{
_timerProvider . Delete ( remove ) ;
}
2016-03-01 05:24:42 +01:00
ActiveRecordingInfo activeRecordingInfo ;
2015-07-20 20:32:55 +02:00
2016-03-01 05:24:42 +01:00
if ( _activeRecordings . TryGetValue ( timerId , out activeRecordingInfo ) )
2015-07-20 20:32:55 +02:00
{
2016-03-01 05:24:42 +01:00
activeRecordingInfo . CancellationTokenSource . Cancel ( ) ;
2015-07-20 20:32:55 +02:00
}
}
public Task CancelTimerAsync ( string timerId , CancellationToken cancellationToken )
{
CancelTimerInternal ( timerId ) ;
return Task . FromResult ( true ) ;
}
2015-07-29 05:42:03 +02:00
public async Task DeleteRecordingAsync ( string recordingId , CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
var remove = _recordingProvider . GetAll ( ) . FirstOrDefault ( i = > string . Equals ( i . Id , recordingId , StringComparison . OrdinalIgnoreCase ) ) ;
if ( remove ! = null )
{
2015-07-29 05:42:03 +02:00
if ( ! string . IsNullOrWhiteSpace ( remove . TimerId ) )
{
var enableDelay = _activeRecordings . ContainsKey ( remove . TimerId ) ;
CancelTimerInternal ( remove . TimerId ) ;
if ( enableDelay )
{
// A hack yes, but need to make sure the file is closed before attempting to delete it
2015-08-16 22:26:49 +02:00
await Task . Delay ( 3000 , cancellationToken ) . ConfigureAwait ( false ) ;
2015-07-29 05:42:03 +02:00
}
}
2016-02-26 03:41:43 +01:00
if ( ! string . IsNullOrWhiteSpace ( remove . Path ) )
2015-07-20 20:32:55 +02:00
{
2016-02-26 03:41:43 +01:00
try
{
_fileSystem . DeleteFile ( remove . Path ) ;
}
catch ( DirectoryNotFoundException )
{
2015-07-20 20:32:55 +02:00
2016-02-26 03:41:43 +01:00
}
catch ( FileNotFoundException )
{
2015-07-20 20:32:55 +02:00
2016-02-26 03:41:43 +01:00
}
2016-03-23 20:06:36 +01:00
catch ( Exception ex )
{
_logger . ErrorException ( "Error deleting recording file {0}" , ex , remove . Path ) ;
}
2015-07-20 20:32:55 +02:00
}
_recordingProvider . Delete ( remove ) ;
}
2015-10-11 02:39:30 +02:00
else
{
throw new ResourceNotFoundException ( "Recording not found: " + recordingId ) ;
}
2015-07-20 20:32:55 +02:00
}
public Task CreateTimerAsync ( TimerInfo info , CancellationToken cancellationToken )
{
info . Id = Guid . NewGuid ( ) . ToString ( "N" ) ;
_timerProvider . Add ( info ) ;
return Task . FromResult ( 0 ) ;
}
2015-08-15 23:58:52 +02:00
public async Task CreateSeriesTimerAsync ( SeriesTimerInfo info , CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
2015-07-29 19:16:00 +02:00
info . Id = Guid . NewGuid ( ) . ToString ( "N" ) ;
2015-07-20 20:32:55 +02:00
2015-08-17 00:03:22 +02:00
List < ProgramInfo > epgData ;
if ( info . RecordAnyChannel )
{
var channels = await GetChannelsAsync ( true , CancellationToken . None ) . ConfigureAwait ( false ) ;
var channelIds = channels . Select ( i = > i . Id ) . ToList ( ) ;
epgData = GetEpgDataForChannels ( channelIds ) ;
}
else
{
epgData = GetEpgDataForChannel ( info . ChannelId ) ;
}
// populate info.seriesID
var program = epgData . FirstOrDefault ( i = > string . Equals ( i . Id , info . ProgramId , StringComparison . OrdinalIgnoreCase ) ) ;
if ( program ! = null )
{
info . SeriesId = program . SeriesId ;
}
else
{
throw new InvalidOperationException ( "SeriesId for program not found" ) ;
}
2015-07-20 20:32:55 +02:00
_seriesTimerProvider . Add ( info ) ;
2016-01-20 04:48:37 +01:00
await UpdateTimersForSeriesTimer ( epgData , info , false ) . ConfigureAwait ( false ) ;
2015-07-20 20:32:55 +02:00
}
2015-08-15 23:58:52 +02:00
public async Task UpdateSeriesTimerAsync ( SeriesTimerInfo info , CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
2016-01-20 04:48:37 +01:00
var instance = _seriesTimerProvider . GetAll ( ) . FirstOrDefault ( i = > string . Equals ( i . Id , info . Id , StringComparison . OrdinalIgnoreCase ) ) ;
if ( instance ! = null )
2015-08-17 00:03:22 +02:00
{
2016-01-20 04:48:37 +01:00
instance . ChannelId = info . ChannelId ;
instance . Days = info . Days ;
instance . EndDate = info . EndDate ;
instance . IsPostPaddingRequired = info . IsPostPaddingRequired ;
instance . IsPrePaddingRequired = info . IsPrePaddingRequired ;
instance . PostPaddingSeconds = info . PostPaddingSeconds ;
instance . PrePaddingSeconds = info . PrePaddingSeconds ;
instance . Priority = info . Priority ;
instance . RecordAnyChannel = info . RecordAnyChannel ;
instance . RecordAnyTime = info . RecordAnyTime ;
instance . RecordNewOnly = info . RecordNewOnly ;
instance . StartDate = info . StartDate ;
_seriesTimerProvider . Update ( instance ) ;
2015-08-17 00:03:22 +02:00
2016-01-20 04:48:37 +01:00
List < ProgramInfo > epgData ;
if ( instance . RecordAnyChannel )
{
var channels = await GetChannelsAsync ( true , CancellationToken . None ) . ConfigureAwait ( false ) ;
var channelIds = channels . Select ( i = > i . Id ) . ToList ( ) ;
epgData = GetEpgDataForChannels ( channelIds ) ;
}
else
{
epgData = GetEpgDataForChannel ( instance . ChannelId ) ;
}
await UpdateTimersForSeriesTimer ( epgData , instance , true ) . ConfigureAwait ( false ) ;
}
2015-07-20 20:32:55 +02:00
}
public Task UpdateTimerAsync ( TimerInfo info , CancellationToken cancellationToken )
{
_timerProvider . Update ( info ) ;
return Task . FromResult ( true ) ;
}
public Task < ImageStream > GetChannelImageAsync ( string channelId , CancellationToken cancellationToken )
{
throw new NotImplementedException ( ) ;
}
public Task < ImageStream > GetRecordingImageAsync ( string recordingId , CancellationToken cancellationToken )
{
throw new NotImplementedException ( ) ;
}
public Task < ImageStream > GetProgramImageAsync ( string programId , string channelId , CancellationToken cancellationToken )
{
throw new NotImplementedException ( ) ;
}
2015-10-11 02:39:30 +02:00
public async Task < IEnumerable < RecordingInfo > > GetRecordingsAsync ( CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
2015-10-11 02:39:30 +02:00
var recordings = _recordingProvider . GetAll ( ) . ToList ( ) ;
var updated = false ;
foreach ( var recording in recordings )
{
if ( recording . Status = = RecordingStatus . InProgress )
{
if ( string . IsNullOrWhiteSpace ( recording . TimerId ) | | ! _activeRecordings . ContainsKey ( recording . TimerId ) )
{
recording . Status = RecordingStatus . Cancelled ;
recording . DateLastUpdated = DateTime . UtcNow ;
_recordingProvider . Update ( recording ) ;
updated = true ;
}
}
}
if ( updated )
{
recordings = _recordingProvider . GetAll ( ) . ToList ( ) ;
}
return recordings ;
2015-07-20 20:32:55 +02:00
}
public Task < IEnumerable < TimerInfo > > GetTimersAsync ( CancellationToken cancellationToken )
{
return Task . FromResult ( ( IEnumerable < TimerInfo > ) _timerProvider . GetAll ( ) ) ;
}
public Task < SeriesTimerInfo > GetNewTimerDefaultsAsync ( CancellationToken cancellationToken , ProgramInfo program = null )
{
2015-08-24 04:08:20 +02:00
var config = GetConfiguration ( ) ;
2015-07-20 20:32:55 +02:00
var defaults = new SeriesTimerInfo ( )
{
2015-08-24 04:08:20 +02:00
PostPaddingSeconds = Math . Max ( config . PostPaddingSeconds , 0 ) ,
PrePaddingSeconds = Math . Max ( config . PrePaddingSeconds , 0 ) ,
2015-07-20 20:32:55 +02:00
RecordAnyChannel = false ,
RecordAnyTime = false ,
RecordNewOnly = false
} ;
2015-08-11 19:47:29 +02:00
if ( program ! = null )
{
defaults . SeriesId = program . SeriesId ;
defaults . ProgramId = program . Id ;
}
2015-07-20 20:32:55 +02:00
return Task . FromResult ( defaults ) ;
}
public Task < IEnumerable < SeriesTimerInfo > > GetSeriesTimersAsync ( CancellationToken cancellationToken )
{
return Task . FromResult ( ( IEnumerable < SeriesTimerInfo > ) _seriesTimerProvider . GetAll ( ) ) ;
}
2015-07-23 07:25:55 +02:00
public async Task < IEnumerable < ProgramInfo > > GetProgramsAsync ( string channelId , DateTime startDateUtc , DateTime endDateUtc , CancellationToken cancellationToken )
2015-09-10 20:28:22 +02:00
{
try
{
return await GetProgramsAsyncInternal ( channelId , startDateUtc , endDateUtc , cancellationToken ) . ConfigureAwait ( false ) ;
}
2015-10-25 19:34:31 +01:00
catch ( OperationCanceledException )
{
throw ;
}
2015-09-10 20:28:22 +02:00
catch ( Exception ex )
{
_logger . ErrorException ( "Error getting programs" , ex ) ;
return GetEpgDataForChannel ( channelId ) . Where ( i = > i . StartDate < = endDateUtc & & i . EndDate > = startDateUtc ) ;
}
}
2016-02-24 20:06:26 +01:00
private bool IsListingProviderEnabledForTuner ( ListingsProviderInfo info , string tunerHostId )
{
2016-03-16 05:14:38 +01:00
if ( info . EnableAllTuners )
{
return true ;
}
if ( string . IsNullOrWhiteSpace ( tunerHostId ) )
{
throw new ArgumentNullException ( "tunerHostId" ) ;
}
return info . EnabledTuners . Contains ( tunerHostId , StringComparer . OrdinalIgnoreCase ) ;
2016-02-24 20:06:26 +01:00
}
2015-09-10 20:28:22 +02:00
private async Task < IEnumerable < ProgramInfo > > GetProgramsAsyncInternal ( string channelId , DateTime startDateUtc , DateTime endDateUtc , CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
2015-08-16 20:54:25 +02:00
var channels = await GetChannelsAsync ( true , cancellationToken ) . ConfigureAwait ( false ) ;
var channel = channels . First ( i = > string . Equals ( i . Id , channelId , StringComparison . OrdinalIgnoreCase ) ) ;
2015-07-23 07:25:55 +02:00
foreach ( var provider in GetListingProviders ( ) )
{
2016-02-24 20:06:26 +01:00
if ( ! IsListingProviderEnabledForTuner ( provider . Item2 , channel . TunerHostId ) )
{
2016-03-17 20:29:53 +01:00
_logger . Debug ( "Skipping getting programs for channel {0}-{1} from {2}-{3}, because it's not enabled for this tuner." , channel . Number , channel . Name , provider . Item1 . Name , provider . Item2 . ListingsId ? ? string . Empty ) ;
2016-02-24 20:06:26 +01:00
continue ;
}
2016-03-17 20:29:53 +01:00
_logger . Debug ( "Getting programs for channel {0}-{1} from {2}-{3}" , channel . Number , channel . Name , provider . Item1 . Name , provider . Item2 . ListingsId ? ? string . Empty ) ;
2016-03-23 20:06:36 +01:00
2015-10-11 02:39:30 +02:00
var programs = await provider . Item1 . GetProgramsAsync ( provider . Item2 , channel . Number , channel . Name , startDateUtc , endDateUtc , cancellationToken )
2015-07-23 07:25:55 +02:00
. ConfigureAwait ( false ) ;
2015-09-10 20:28:22 +02:00
2015-07-23 07:25:55 +02:00
var list = programs . ToList ( ) ;
2015-07-24 01:40:54 +02:00
// Replace the value that came from the provider with a normalized value
foreach ( var program in list )
{
program . ChannelId = channelId ;
}
2015-07-23 07:25:55 +02:00
if ( list . Count > 0 )
{
2015-07-29 05:42:03 +02:00
SaveEpgDataForChannel ( channelId , list ) ;
2015-07-23 07:25:55 +02:00
return list ;
}
}
return new List < ProgramInfo > ( ) ;
}
private List < Tuple < IListingsProvider , ListingsProviderInfo > > GetListingProviders ( )
{
return GetConfiguration ( ) . ListingProviders
. Select ( i = >
{
2015-07-23 15:23:22 +02:00
var provider = _liveTvManager . ListingProviders . FirstOrDefault ( l = > string . Equals ( l . Type , i . Type , StringComparison . OrdinalIgnoreCase ) ) ;
2015-07-23 07:25:55 +02:00
return provider = = null ? null : new Tuple < IListingsProvider , ListingsProviderInfo > ( provider , i ) ;
} )
. Where ( i = > i ! = null )
. ToList ( ) ;
2015-07-20 20:32:55 +02:00
}
public Task < MediaSourceInfo > GetRecordingStream ( string recordingId , string streamId , CancellationToken cancellationToken )
{
throw new NotImplementedException ( ) ;
}
2015-07-24 01:40:54 +02:00
public async Task < MediaSourceInfo > GetChannelStream ( string channelId , string streamId , CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
2015-07-24 01:40:54 +02:00
_logger . Info ( "Streaming Channel " + channelId ) ;
2015-10-16 20:11:11 +02:00
foreach ( var hostInstance in _liveTvManager . TunerHosts )
{
try
{
var result = await hostInstance . GetChannelStream ( channelId , streamId , cancellationToken ) . ConfigureAwait ( false ) ;
result . Item2 . Release ( ) ;
return result . Item1 ;
}
catch ( Exception e )
{
_logger . ErrorException ( "Error getting channel stream" , e ) ;
}
}
throw new ApplicationException ( "Tuner not found." ) ;
}
2016-04-26 04:16:46 +02:00
private async Task < Tuple < MediaSourceInfo , ITunerHost , SemaphoreSlim > > GetChannelStreamInternal ( string channelId , string streamId , CancellationToken cancellationToken )
2015-10-16 20:11:11 +02:00
{
_logger . Info ( "Streaming Channel " + channelId ) ;
2015-08-19 21:25:18 +02:00
foreach ( var hostInstance in _liveTvManager . TunerHosts )
2015-07-24 01:40:54 +02:00
{
try
{
2016-04-26 04:16:46 +02:00
var result = await hostInstance . GetChannelStream ( channelId , streamId , cancellationToken ) . ConfigureAwait ( false ) ;
return new Tuple < MediaSourceInfo , ITunerHost , SemaphoreSlim > ( result . Item1 , hostInstance , result . Item2 ) ;
2015-07-24 01:40:54 +02:00
}
2015-08-16 20:37:53 +02:00
catch ( Exception e )
2015-07-24 01:40:54 +02:00
{
2015-08-16 20:37:53 +02:00
_logger . ErrorException ( "Error getting channel stream" , e ) ;
2015-07-24 01:40:54 +02:00
}
}
throw new ApplicationException ( "Tuner not found." ) ;
2015-07-20 20:32:55 +02:00
}
2015-07-24 01:40:54 +02:00
public async Task < List < MediaSourceInfo > > GetChannelStreamMediaSources ( string channelId , CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
2015-08-19 21:25:18 +02:00
foreach ( var hostInstance in _liveTvManager . TunerHosts )
2015-07-24 01:40:54 +02:00
{
2015-08-16 20:37:53 +02:00
try
2015-07-24 01:40:54 +02:00
{
2015-08-19 21:25:18 +02:00
var sources = await hostInstance . GetChannelStreamMediaSources ( channelId , cancellationToken ) . ConfigureAwait ( false ) ;
2015-07-24 01:40:54 +02:00
2015-08-16 20:37:53 +02:00
if ( sources . Count > 0 )
{
return sources ;
}
}
catch ( NotImplementedException )
{
2015-08-16 20:54:25 +02:00
2015-07-24 01:40:54 +02:00
}
}
2015-08-27 21:59:42 +02:00
throw new NotImplementedException ( ) ;
2015-07-20 20:32:55 +02:00
}
public Task < List < MediaSourceInfo > > GetRecordingStreamMediaSources ( string recordingId , CancellationToken cancellationToken )
{
throw new NotImplementedException ( ) ;
}
public Task CloseLiveStream ( string id , CancellationToken cancellationToken )
{
return Task . FromResult ( 0 ) ;
}
public Task RecordLiveStream ( string id , CancellationToken cancellationToken )
{
return Task . FromResult ( 0 ) ;
}
public Task ResetTuner ( string id , CancellationToken cancellationToken )
{
return Task . FromResult ( 0 ) ;
}
async void _timerProvider_TimerFired ( object sender , GenericEventArgs < TimerInfo > e )
{
2015-08-22 20:29:12 +02:00
var timer = e . Argument ;
2015-09-01 01:47:47 +02:00
_logger . Info ( "Recording timer fired." ) ;
2015-09-21 18:23:20 +02:00
2015-07-20 20:32:55 +02:00
try
{
2015-09-26 04:31:13 +02:00
var recordingEndDate = timer . EndDate . AddSeconds ( timer . PostPaddingSeconds ) ;
if ( recordingEndDate < = DateTime . UtcNow )
{
_logger . Warn ( "Recording timer fired for timer {0}, Id: {1}, but the program has already ended." , timer . Name , timer . Id ) ;
return ;
}
2016-03-01 05:24:42 +01:00
var activeRecordingInfo = new ActiveRecordingInfo
{
CancellationTokenSource = new CancellationTokenSource ( ) ,
TimerId = timer . Id
} ;
2015-07-20 20:32:55 +02:00
2016-03-01 05:24:42 +01:00
if ( _activeRecordings . TryAdd ( timer . Id , activeRecordingInfo ) )
2015-07-20 20:32:55 +02:00
{
2016-03-01 05:24:42 +01:00
await RecordStream ( timer , recordingEndDate , activeRecordingInfo , activeRecordingInfo . CancellationTokenSource . Token ) . ConfigureAwait ( false ) ;
2015-07-20 20:32:55 +02:00
}
2016-01-26 19:18:54 +01:00
else
{
_logger . Info ( "Skipping RecordStream because it's already in progress." ) ;
}
2015-07-20 20:32:55 +02:00
}
catch ( OperationCanceledException )
{
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error recording stream" , ex ) ;
}
}
2016-03-01 05:24:42 +01:00
private async Task RecordStream ( TimerInfo timer , DateTime recordingEndDate , ActiveRecordingInfo activeRecordingInfo , CancellationToken cancellationToken )
2015-07-20 20:32:55 +02:00
{
2015-07-28 05:32:33 +02:00
if ( timer = = null )
{
throw new ArgumentNullException ( "timer" ) ;
}
2016-02-06 22:32:02 +01:00
ProgramInfo info = null ;
2015-10-21 07:09:50 +02:00
if ( string . IsNullOrWhiteSpace ( timer . ProgramId ) )
{
2016-02-06 22:32:02 +01:00
_logger . Info ( "Timer {0} has null programId" , timer . Id ) ;
}
else
{
info = GetProgramInfoFromCache ( timer . ChannelId , timer . ProgramId ) ;
2015-10-21 07:09:50 +02:00
}
2016-02-06 22:32:02 +01:00
if ( info = = null )
{
_logger . Info ( "Unable to find program with Id {0}. Will search using start date" , timer . ProgramId ) ;
info = GetProgramInfoFromCache ( timer . ChannelId , timer . StartDate ) ;
}
2015-09-26 04:31:13 +02:00
2015-10-21 07:09:50 +02:00
if ( info = = null )
{
throw new InvalidOperationException ( string . Format ( "Program with Id {0} not found" , timer . ProgramId ) ) ;
}
2015-07-20 20:32:55 +02:00
var recordPath = RecordingPath ;
2015-08-21 01:55:23 +02:00
2015-07-20 20:32:55 +02:00
if ( info . IsMovie )
{
2015-09-27 23:02:39 +02:00
recordPath = Path . Combine ( recordPath , "Movies" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
2015-07-20 20:32:55 +02:00
}
2015-08-21 01:55:23 +02:00
else if ( info . IsSeries )
{
2015-09-27 23:02:39 +02:00
recordPath = Path . Combine ( recordPath , "Series" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
2016-05-03 06:17:57 +02:00
if ( info . SeasonNumber . HasValue )
{
var folderName = string . Format ( "Season {0}" , info . SeasonNumber . Value . ToString ( CultureInfo . InvariantCulture ) ) ;
recordPath = Path . Combine ( recordPath , folderName ) ;
}
2015-08-21 01:55:23 +02:00
}
else if ( info . IsKids )
{
2015-09-27 23:02:39 +02:00
recordPath = Path . Combine ( recordPath , "Kids" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
2015-08-21 01:55:23 +02:00
}
else if ( info . IsSports )
{
2015-09-27 23:02:39 +02:00
recordPath = Path . Combine ( recordPath , "Sports" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
2015-08-21 01:55:23 +02:00
}
2015-07-20 20:32:55 +02:00
else
{
2015-09-27 23:02:39 +02:00
recordPath = Path . Combine ( recordPath , "Other" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
2015-07-20 20:32:55 +02:00
}
2015-09-27 23:02:39 +02:00
var recordingFileName = _fileSystem . GetValidFilename ( RecordingHelper . GetRecordingName ( timer , info ) ) . Trim ( ) + ".ts" ;
2015-08-21 04:36:30 +02:00
recordPath = Path . Combine ( recordPath , recordingFileName ) ;
2015-07-20 20:32:55 +02:00
2015-10-01 18:28:24 +02:00
var recordingId = info . Id . GetMD5 ( ) . ToString ( "N" ) ;
var recording = _recordingProvider . GetAll ( ) . FirstOrDefault ( x = > string . Equals ( x . Id , recordingId , StringComparison . OrdinalIgnoreCase ) ) ;
2015-07-20 20:32:55 +02:00
if ( recording = = null )
{
2015-08-04 16:26:36 +02:00
recording = new RecordingInfo
2015-07-20 20:32:55 +02:00
{
ChannelId = info . ChannelId ,
2015-10-01 18:28:24 +02:00
Id = recordingId ,
2015-07-20 20:32:55 +02:00
StartDate = info . StartDate ,
EndDate = info . EndDate ,
2015-08-04 16:26:36 +02:00
Genres = info . Genres ,
2015-07-20 20:32:55 +02:00
IsKids = info . IsKids ,
IsLive = info . IsLive ,
IsMovie = info . IsMovie ,
IsHD = info . IsHD ,
IsNews = info . IsNews ,
IsPremiere = info . IsPremiere ,
IsSeries = info . IsSeries ,
IsSports = info . IsSports ,
IsRepeat = ! info . IsPremiere ,
Name = info . Name ,
2015-08-04 16:26:36 +02:00
EpisodeTitle = info . EpisodeTitle ,
2015-07-20 20:32:55 +02:00
ProgramId = info . Id ,
2015-08-04 16:26:36 +02:00
ImagePath = info . ImagePath ,
2015-07-20 20:32:55 +02:00
ImageUrl = info . ImageUrl ,
OriginalAirDate = info . OriginalAirDate ,
Status = RecordingStatus . Scheduled ,
Overview = info . Overview ,
2015-08-11 19:47:29 +02:00
SeriesTimerId = timer . SeriesTimerId ,
TimerId = timer . Id ,
ShowId = info . ShowId
2015-07-20 20:32:55 +02:00
} ;
2015-11-21 05:34:55 +01:00
_recordingProvider . AddOrUpdate ( recording ) ;
2015-07-20 20:32:55 +02:00
}
try
{
2016-02-05 04:44:44 +01:00
var result = await GetChannelStreamInternal ( timer . ChannelId , null , CancellationToken . None ) . ConfigureAwait ( false ) ;
2015-10-16 20:11:11 +02:00
var mediaStreamInfo = result . Item1 ;
var isResourceOpen = true ;
2015-09-26 04:31:13 +02:00
2015-10-16 20:11:11 +02:00
// Unfortunately due to the semaphore we have to have a nested try/finally
try
2015-09-26 04:31:13 +02:00
{
2015-10-16 20:11:11 +02:00
// HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
2016-02-29 17:25:21 +01:00
//await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
2015-09-26 04:31:13 +02:00
2016-02-12 08:01:38 +01:00
var recorder = await GetRecorder ( ) . ConfigureAwait ( false ) ;
if ( recorder is EncodedRecorder )
2015-10-16 20:11:11 +02:00
{
2016-02-12 08:01:38 +01:00
recordPath = Path . ChangeExtension ( recordPath , ".mp4" ) ;
}
2016-03-01 05:24:42 +01:00
recordPath = EnsureFileUnique ( recordPath , timer . Id ) ;
_fileSystem . CreateDirectory ( Path . GetDirectoryName ( recordPath ) ) ;
activeRecordingInfo . Path = recordPath ;
2015-10-16 20:11:11 +02:00
2016-02-22 17:00:17 +01:00
_libraryMonitor . ReportFileSystemChangeBeginning ( recordPath ) ;
2015-10-16 20:11:11 +02:00
recording . Path = recordPath ;
recording . Status = RecordingStatus . InProgress ;
recording . DateLastUpdated = DateTime . UtcNow ;
2015-11-21 05:34:55 +01:00
_recordingProvider . AddOrUpdate ( recording ) ;
2015-10-16 20:11:11 +02:00
2016-04-26 04:16:46 +02:00
var duration = recordingEndDate - DateTime . UtcNow ;
2016-01-26 19:18:54 +01:00
_logger . Info ( "Beginning recording. Will record for {0} minutes." , duration . TotalMinutes . ToString ( CultureInfo . InvariantCulture ) ) ;
2015-10-16 20:11:11 +02:00
_logger . Info ( "Writing file to path: " + recordPath ) ;
2016-02-05 04:44:44 +01:00
_logger . Info ( "Opening recording stream from tuner provider" ) ;
2016-02-06 22:32:02 +01:00
2016-02-12 08:01:38 +01:00
Action onStarted = ( ) = >
{
2016-04-26 04:16:46 +02:00
result . Item3 . Release ( ) ;
2016-02-12 08:01:38 +01:00
isResourceOpen = false ;
} ;
2016-02-06 22:32:02 +01:00
2016-04-26 04:16:46 +02:00
var pathWithDuration = result . Item2 . ApplyDuration ( mediaStreamInfo . Path , duration ) ;
// If it supports supplying duration via url
if ( ! string . Equals ( pathWithDuration , mediaStreamInfo . Path , StringComparison . OrdinalIgnoreCase ) )
{
mediaStreamInfo . Path = pathWithDuration ;
mediaStreamInfo . RunTimeTicks = duration . Ticks ;
}
2016-03-01 05:24:42 +01:00
await recorder . Record ( mediaStreamInfo , recordPath , duration , onStarted , cancellationToken ) . ConfigureAwait ( false ) ;
2015-09-26 04:31:13 +02:00
2015-10-16 20:11:11 +02:00
recording . Status = RecordingStatus . Completed ;
2016-03-01 05:24:42 +01:00
_logger . Info ( "Recording completed: {0}" , recordPath ) ;
2015-10-16 20:11:11 +02:00
}
finally
2015-07-20 20:32:55 +02:00
{
2015-10-16 20:11:11 +02:00
if ( isResourceOpen )
2015-07-20 20:32:55 +02:00
{
2016-04-26 04:16:46 +02:00
result . Item3 . Release ( ) ;
2015-07-20 20:32:55 +02:00
}
2016-02-22 17:00:17 +01:00
_libraryMonitor . ReportFileSystemChangeComplete ( recordPath , false ) ;
2015-07-20 20:32:55 +02:00
}
}
catch ( OperationCanceledException )
{
2016-03-01 05:24:42 +01:00
_logger . Info ( "Recording stopped: {0}" , recordPath ) ;
2015-07-29 22:31:15 +02:00
recording . Status = RecordingStatus . Completed ;
2015-07-20 20:32:55 +02:00
}
2015-08-05 05:43:54 +02:00
catch ( Exception ex )
2015-07-20 20:32:55 +02:00
{
2016-03-01 05:24:42 +01:00
_logger . ErrorException ( "Error recording to {0}" , ex , recordPath ) ;
2015-07-20 20:32:55 +02:00
recording . Status = RecordingStatus . Error ;
}
2015-09-04 03:34:57 +02:00
finally
{
2016-03-01 05:24:42 +01:00
ActiveRecordingInfo removed ;
2015-09-04 03:34:57 +02:00
_activeRecordings . TryRemove ( timer . Id , out removed ) ;
}
2015-07-20 20:32:55 +02:00
2015-08-18 17:25:57 +02:00
recording . DateLastUpdated = DateTime . UtcNow ;
2015-11-21 05:34:55 +01:00
_recordingProvider . AddOrUpdate ( recording ) ;
2015-08-22 21:46:55 +02:00
if ( recording . Status = = RecordingStatus . Completed )
{
OnSuccessfulRecording ( recording ) ;
2015-09-03 03:40:47 +02:00
_timerProvider . Delete ( timer ) ;
}
2015-09-04 03:34:57 +02:00
else if ( DateTime . UtcNow < timer . EndDate )
2015-09-03 03:40:47 +02:00
{
2015-09-04 03:34:57 +02:00
const int retryIntervalSeconds = 60 ;
_logger . Info ( "Retrying recording in {0} seconds." , retryIntervalSeconds ) ;
2015-09-03 03:40:47 +02:00
2015-09-04 03:34:57 +02:00
_timerProvider . StartTimer ( timer , TimeSpan . FromSeconds ( retryIntervalSeconds ) ) ;
}
else
{
_timerProvider . Delete ( timer ) ;
_recordingProvider . Delete ( recording ) ;
2015-08-22 21:46:55 +02:00
}
}
2016-03-01 05:24:42 +01:00
private string EnsureFileUnique ( string path , string timerId )
2016-02-26 05:09:42 +01:00
{
var originalPath = path ;
var index = 1 ;
2016-03-01 05:24:42 +01:00
while ( FileExists ( path , timerId ) )
2016-02-26 05:09:42 +01:00
{
var parent = Path . GetDirectoryName ( originalPath ) ;
var name = Path . GetFileNameWithoutExtension ( originalPath ) ;
name + = "-" + index . ToString ( CultureInfo . InvariantCulture ) ;
path = Path . ChangeExtension ( Path . Combine ( parent , name ) , Path . GetExtension ( originalPath ) ) ;
index + + ;
}
return path ;
}
2016-03-01 05:24:42 +01:00
private bool FileExists ( string path , string timerId )
{
if ( _fileSystem . FileExists ( path ) )
{
return true ;
}
var hasRecordingAtPath = _activeRecordings . Values . ToList ( ) . Any ( i = > string . Equals ( i . Path , path , StringComparison . OrdinalIgnoreCase ) & & ! string . Equals ( i . TimerId , timerId , StringComparison . OrdinalIgnoreCase ) ) ;
if ( hasRecordingAtPath )
{
return true ;
}
return false ;
}
2016-02-12 08:01:38 +01:00
private async Task < IRecorder > GetRecorder ( )
{
2016-04-27 23:26:28 +02:00
var config = GetConfiguration ( ) ;
if ( config . EnableRecordingEncoding )
2016-02-12 08:01:38 +01:00
{
2016-02-12 19:29:28 +01:00
var regInfo = await _security . GetRegistrationStatus ( "embytvrecordingconversion" ) . ConfigureAwait ( false ) ;
2016-02-12 08:01:38 +01:00
if ( regInfo . IsValid )
{
2016-04-27 23:26:28 +02:00
return new EncodedRecorder ( _logger , _fileSystem , _mediaEncoder , _config . ApplicationPaths , _jsonSerializer , config ) ;
2016-02-12 08:01:38 +01:00
}
}
return new DirectRecorder ( _logger , _httpClient , _fileSystem ) ;
}
2015-08-22 21:46:55 +02:00
private async void OnSuccessfulRecording ( RecordingInfo recording )
{
if ( GetConfiguration ( ) . EnableAutoOrganize )
{
if ( recording . IsSeries )
{
try
{
2016-03-03 18:52:47 +01:00
// this is to account for the library monitor holding a lock for additional time after the change is complete.
// ideally this shouldn't be hard-coded
await Task . Delay ( 30000 ) . ConfigureAwait ( false ) ;
2015-08-22 21:46:55 +02:00
var organize = new EpisodeFileOrganizer ( _organizationService , _config , _fileSystem , _logger , _libraryManager , _libraryMonitor , _providerManager ) ;
var result = await organize . OrganizeEpisodeFile ( recording . Path , CancellationToken . None ) . ConfigureAwait ( false ) ;
if ( result . Status = = FileSortingStatus . Success )
{
_recordingProvider . Delete ( recording ) ;
}
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error processing new recording" , ex ) ;
}
}
}
2015-07-20 20:32:55 +02:00
}
private ProgramInfo GetProgramInfoFromCache ( string channelId , string programId )
{
var epgData = GetEpgDataForChannel ( channelId ) ;
2015-08-18 06:22:45 +02:00
return epgData . FirstOrDefault ( p = > string . Equals ( p . Id , programId , StringComparison . OrdinalIgnoreCase ) ) ;
2015-07-20 20:32:55 +02:00
}
2016-02-06 22:32:02 +01:00
private ProgramInfo GetProgramInfoFromCache ( string channelId , DateTime startDateUtc )
{
var epgData = GetEpgDataForChannel ( channelId ) ;
var startDateTicks = startDateUtc . Ticks ;
// Find the first program that starts within 3 minutes
return epgData . FirstOrDefault ( p = > Math . Abs ( startDateTicks - p . StartDate . Ticks ) < = TimeSpan . FromMinutes ( 3 ) . Ticks ) ;
}
2015-07-20 20:32:55 +02:00
private string RecordingPath
{
get
{
var path = GetConfiguration ( ) . RecordingPath ;
return string . IsNullOrWhiteSpace ( path )
? Path . Combine ( DataPath , "recordings" )
: path ;
}
}
private LiveTvOptions GetConfiguration ( )
{
return _config . GetConfiguration < LiveTvOptions > ( "livetv" ) ;
}
2016-01-20 04:48:37 +01:00
private async Task UpdateTimersForSeriesTimer ( List < ProgramInfo > epgData , SeriesTimerInfo seriesTimer , bool deleteInvalidTimers )
2015-07-20 20:32:55 +02:00
{
2015-11-21 05:34:55 +01:00
var newTimers = GetTimersForSeries ( seriesTimer , epgData , _recordingProvider . GetAll ( ) ) . ToList ( ) ;
2015-08-21 21:25:35 +02:00
var registration = await GetRegistrationInfo ( "seriesrecordings" ) . ConfigureAwait ( false ) ;
if ( registration . IsValid )
2015-07-20 20:32:55 +02:00
{
2015-08-21 21:25:35 +02:00
foreach ( var timer in newTimers )
{
_timerProvider . AddOrUpdate ( timer ) ;
}
2015-07-20 20:32:55 +02:00
}
2016-01-20 04:48:37 +01:00
if ( deleteInvalidTimers )
{
var allTimers = GetTimersForSeries ( seriesTimer , epgData , new List < RecordingInfo > ( ) )
. Select ( i = > i . Id )
. ToList ( ) ;
var deletes = _timerProvider . GetAll ( )
. Where ( i = > string . Equals ( i . SeriesTimerId , seriesTimer . Id , StringComparison . OrdinalIgnoreCase ) )
. Where ( i = > ! allTimers . Contains ( i . Id , StringComparer . OrdinalIgnoreCase ) & & i . StartDate > DateTime . UtcNow )
. ToList ( ) ;
foreach ( var timer in deletes )
{
await CancelTimerAsync ( timer . Id , CancellationToken . None ) . ConfigureAwait ( false ) ;
}
}
2015-07-20 20:32:55 +02:00
}
2015-07-29 19:16:00 +02:00
private IEnumerable < TimerInfo > GetTimersForSeries ( SeriesTimerInfo seriesTimer , IEnumerable < ProgramInfo > allPrograms , IReadOnlyList < RecordingInfo > currentRecordings )
{
2016-03-13 08:34:17 +01:00
if ( seriesTimer = = null )
{
throw new ArgumentNullException ( "seriesTimer" ) ;
}
if ( allPrograms = = null )
{
throw new ArgumentNullException ( "allPrograms" ) ;
}
if ( currentRecordings = = null )
{
throw new ArgumentNullException ( "currentRecordings" ) ;
}
2015-08-17 18:52:56 +02:00
// Exclude programs that have already ended
2016-01-20 04:48:37 +01:00
allPrograms = allPrograms . Where ( i = > i . EndDate > DateTime . UtcNow & & i . StartDate > DateTime . UtcNow ) ;
2015-08-17 18:52:56 +02:00
2015-07-29 19:16:00 +02:00
allPrograms = GetProgramsForSeries ( seriesTimer , allPrograms ) ;
2015-08-18 06:22:45 +02:00
var recordingShowIds = currentRecordings . Select ( i = > i . ProgramId ) . Where ( i = > ! string . IsNullOrWhiteSpace ( i ) ) . ToList ( ) ;
2015-08-11 19:47:29 +02:00
2015-08-18 06:22:45 +02:00
allPrograms = allPrograms . Where ( i = > ! recordingShowIds . Contains ( i . Id , StringComparer . OrdinalIgnoreCase ) ) ;
2015-07-29 19:16:00 +02:00
return allPrograms . Select ( i = > RecordingHelper . CreateTimer ( i , seriesTimer ) ) ;
}
private IEnumerable < ProgramInfo > GetProgramsForSeries ( SeriesTimerInfo seriesTimer , IEnumerable < ProgramInfo > allPrograms )
{
if ( ! seriesTimer . RecordAnyTime )
{
2015-12-07 16:41:22 +01:00
allPrograms = allPrograms . Where ( epg = > Math . Abs ( seriesTimer . StartDate . TimeOfDay . Ticks - epg . StartDate . TimeOfDay . Ticks ) < TimeSpan . FromMinutes ( 5 ) . Ticks ) ;
2015-07-29 19:16:00 +02:00
}
if ( seriesTimer . RecordNewOnly )
{
2015-08-16 20:37:53 +02:00
allPrograms = allPrograms . Where ( epg = > ! epg . IsRepeat ) ;
2015-07-29 19:16:00 +02:00
}
if ( ! seriesTimer . RecordAnyChannel )
{
allPrograms = allPrograms . Where ( epg = > string . Equals ( epg . ChannelId , seriesTimer . ChannelId , StringComparison . OrdinalIgnoreCase ) ) ;
}
2015-08-18 18:32:18 +02:00
allPrograms = allPrograms . Where ( i = > seriesTimer . Days . Contains ( i . StartDate . ToLocalTime ( ) . DayOfWeek ) ) ;
2015-07-29 19:16:00 +02:00
2015-08-16 20:37:53 +02:00
if ( string . IsNullOrWhiteSpace ( seriesTimer . SeriesId ) )
{
_logger . Error ( "seriesTimer.SeriesId is null. Cannot find programs for series" ) ;
return new List < ProgramInfo > ( ) ;
}
2015-08-15 23:58:52 +02:00
return allPrograms . Where ( i = > string . Equals ( i . SeriesId , seriesTimer . SeriesId , StringComparison . OrdinalIgnoreCase ) ) ;
2015-07-29 19:16:00 +02:00
}
2015-08-03 01:47:31 +02:00
2015-07-20 20:32:55 +02:00
private string GetChannelEpgCachePath ( string channelId )
{
2015-10-24 17:33:22 +02:00
return Path . Combine ( _config . CommonApplicationPaths . CachePath , "embytvepg" , channelId + ".json" ) ;
2015-07-20 20:32:55 +02:00
}
private readonly object _epgLock = new object ( ) ;
private void SaveEpgDataForChannel ( string channelId , List < ProgramInfo > epgData )
{
var path = GetChannelEpgCachePath ( channelId ) ;
2015-09-21 18:26:05 +02:00
_fileSystem . CreateDirectory ( Path . GetDirectoryName ( path ) ) ;
2015-07-20 20:32:55 +02:00
lock ( _epgLock )
{
_jsonSerializer . SerializeToFile ( epgData , path ) ;
}
}
private List < ProgramInfo > GetEpgDataForChannel ( string channelId )
{
try
{
lock ( _epgLock )
{
return _jsonSerializer . DeserializeFromFile < List < ProgramInfo > > ( GetChannelEpgCachePath ( channelId ) ) ;
}
}
catch
{
return new List < ProgramInfo > ( ) ;
}
}
2015-08-15 23:58:52 +02:00
private List < ProgramInfo > GetEpgDataForChannels ( List < string > channelIds )
2015-07-20 20:32:55 +02:00
{
2015-08-15 23:58:52 +02:00
return channelIds . SelectMany ( GetEpgDataForChannel ) . ToList ( ) ;
2015-07-20 20:32:55 +02:00
}
public void Dispose ( )
{
foreach ( var pair in _activeRecordings . ToList ( ) )
{
2016-03-01 05:24:42 +01:00
pair . Value . CancellationTokenSource . Cancel ( ) ;
2015-07-20 20:32:55 +02:00
}
}
2015-08-21 21:25:35 +02:00
public Task < MBRegistrationRecord > GetRegistrationInfo ( string feature )
{
if ( string . Equals ( feature , "seriesrecordings" , StringComparison . OrdinalIgnoreCase ) )
{
return _security . GetRegistrationStatus ( "embytvseriesrecordings" ) ;
}
return Task . FromResult ( new MBRegistrationRecord
{
IsValid = true ,
IsRegistered = true
} ) ;
}
2016-03-01 05:24:42 +01:00
class ActiveRecordingInfo
{
public string Path { get ; set ; }
public string TimerId { get ; set ; }
public CancellationTokenSource CancellationTokenSource { get ; set ; }
}
2015-07-20 20:32:55 +02:00
}
2015-09-21 18:26:05 +02:00
}