@ -26,7 +26,10 @@ using System.Linq;
using System.Threading ;
using System.Threading.Tasks ;
using CommonIO ;
using MediaBrowser.Common.Events ;
using MediaBrowser.Common.Extensions ;
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Entities.TV ;
using MediaBrowser.Controller.Power ;
using Microsoft.Win32 ;
@ -40,7 +43,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly IServerConfigurationManager _config ;
private readonly IJsonSerializer _jsonSerializer ;
private readonly ItemDataProvider < RecordingInfo > _recordingProvider ;
private readonly ItemDataProvider < SeriesTimerInfo > _seriesTimerProvider ;
private readonly TimerManager _timerProvider ;
@ -56,6 +58,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public static EmbyTV Current ;
public event EventHandler DataSourceChanged ;
public event EventHandler < RecordingStatusChangedEventArgs > RecordingStatusChanged ;
private readonly ConcurrentDictionary < string , ActiveRecordingInfo > _activeRecordings =
new ConcurrentDictionary < string , ActiveRecordingInfo > ( StringComparer . OrdinalIgnoreCase ) ;
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 )
{
Current = this ;
@ -74,10 +82,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_liveTvManager = ( LiveTvManager ) liveTvManager ;
_jsonSerializer = jsonSerializer ;
_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" ) ) ;
_timerProvider = new TimerManager ( fileSystem , jsonSerializer , _logger , Path . Combine ( DataPath , "timers" ) , powerManagement , _logger ) ;
_timerProvider . TimerFired + = _timerProvider_TimerFired ;
_config . NamedConfigurationUpdated + = _config_NamedConfigurationUpdated ;
}
private void _config_NamedConfigurationUpdated ( object sender , ConfigurationUpdateEventArgs e )
{
if ( string . Equals ( e . Key , "livetv" , StringComparison . OrdinalIgnoreCase ) )
{
OnRecordingFoldersChanged ( ) ;
}
}
public void Start ( )
@ -85,6 +102,95 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_timerProvider . RestartTimers ( ) ;
SystemEvents . PowerModeChanged + = SystemEvents_PowerModeChanged ;
CreateRecordingFolders ( ) ;
}
private void OnRecordingFoldersChanged ( )
{
CreateRecordingFolders ( ) ;
}
private void CreateRecordingFolders ( )
{
var recordingFolders = GetRecordingFolders ( ) ;
var defaultRecordingPath = DefaultRecordingPath ;
if ( ! recordingFolders . Any ( i = > i . Locations . Contains ( defaultRecordingPath , StringComparer . OrdinalIgnoreCase ) ) )
{
RemovePathFromLibrary ( defaultRecordingPath ) ;
}
var virtualFolders = _libraryManager . GetVirtualFolders ( )
. ToList ( ) ;
var allExistingPaths = virtualFolders . SelectMany ( i = > i . Locations ) . ToList ( ) ;
foreach ( var recordingFolder in recordingFolders )
{
var pathsToCreate = recordingFolder . Locations
. Where ( i = > ! allExistingPaths . Contains ( i , StringComparer . OrdinalIgnoreCase ) )
. ToList ( ) ;
if ( pathsToCreate . Count = = 0 )
{
continue ;
}
try
{
_libraryManager . AddVirtualFolder ( recordingFolder . Name , recordingFolder . CollectionType , pathsToCreate . ToArray ( ) , true ) ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error creating virtual folder" , ex ) ;
}
}
}
private void RemovePathFromLibrary ( string path )
{
var requiresRefresh = false ;
var virtualFolders = _libraryManager . GetVirtualFolders ( )
. ToList ( ) ;
foreach ( var virtualFolder in virtualFolders )
{
if ( ! virtualFolder . Locations . Contains ( path , StringComparer . OrdinalIgnoreCase ) )
{
continue ;
}
if ( virtualFolder . Locations . Count = = 1 )
{
// remove entire virtual folder
try
{
_libraryManager . RemoveVirtualFolder ( virtualFolder . Name , true ) ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error removing virtual folder" , ex ) ;
}
}
else
{
try
{
_libraryManager . RemoveMediaPath ( virtualFolder . Name , path ) ;
requiresRefresh = true ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error removing media path" , ex ) ;
}
}
}
if ( requiresRefresh )
{
_libraryManager . ValidateMediaLibrary ( new Progress < Double > ( ) , CancellationToken . None ) ;
}
}
void SystemEvents_PowerModeChanged ( object sender , PowerModeChangedEventArgs e )
@ -97,13 +203,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
public event EventHandler DataSourceChanged ;
public event EventHandler < RecordingStatusChangedEventArgs > RecordingStatusChanged ;
private readonly ConcurrentDictionary < string , ActiveRecordingInfo > _activeRecordings =
new ConcurrentDictionary < string , ActiveRecordingInfo > ( StringComparer . OrdinalIgnoreCase ) ;
public string Name
{
get { return "Emby" ; }
@ -114,6 +213,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
get { return Path . Combine ( _config . CommonApplicationPaths . DataPath , "livetv" ) ; }
}
private string DefaultRecordingPath
{
get
{
return Path . Combine ( DataPath , "recordings" ) ;
}
}
private string RecordingPath
{
get
{
var path = GetConfiguration ( ) . RecordingPath ;
return string . IsNullOrWhiteSpace ( path )
? DefaultRecordingPath
: path ;
}
}
public string HomePageUrl
{
get { return "http://emby.media" ; }
@ -280,49 +399,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return Task . FromResult ( true ) ;
}
public async Task DeleteRecordingAsync ( string recordingId , CancellationToken cancellationToken )
public Task DeleteRecordingAsync ( string recordingId , CancellationToken cancellationToken )
{
var remove = _recordingProvider . GetAll ( ) . FirstOrDefault ( i = > string . Equals ( i . Id , recordingId , StringComparison . OrdinalIgnoreCase ) ) ;
if ( remove ! = null )
{
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
await Task . Delay ( 3000 , cancellationToken ) . ConfigureAwait ( false ) ;
}
}
if ( ! string . IsNullOrWhiteSpace ( remove . Path ) )
{
try
{
_fileSystem . DeleteFile ( remove . Path ) ;
}
catch ( DirectoryNotFoundException )
{
}
catch ( FileNotFoundException )
{
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error deleting recording file {0}" , ex , remove . Path ) ;
}
}
_recordingProvider . Delete ( remove ) ;
}
else
{
throw new ResourceNotFoundException ( "Recording not found: " + recordingId ) ;
}
return Task . FromResult ( true ) ;
}
public Task CreateTimerAsync ( TimerInfo info , CancellationToken cancellationToken )
@ -424,29 +503,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public async Task < IEnumerable < RecordingInfo > > GetRecordingsAsync ( CancellationToken cancellationToken )
{
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 ;
return new List < RecordingInfo > ( ) ;
}
public Task < IEnumerable < TimerInfo > > GetTimersAsync ( CancellationToken cancellationToken )
@ -695,104 +752,124 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
private async Task RecordStream ( TimerInfo timer , DateTime recordingEndDate , ActiveRecordingInfo activeRecordingInfo , CancellationToken cancellationToken )
private string GetRecordingPath ( TimerInfo timer , ProgramInfo info )
{
if ( timer = = null )
{
throw new ArgumentNullException ( "timer" ) ;
}
ProgramInfo info = null ;
if ( string . IsNullOrWhiteSpace ( timer . ProgramId ) )
{
_logger . Info ( "Timer {0} has null programId" , timer . Id ) ;
}
else
{
info = GetProgramInfoFromCache ( timer . ChannelId , timer . ProgramId ) ;
}
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 ) ;
}
if ( info = = null )
{
throw new InvalidOperationException ( string . Format ( "Program with Id {0} not found" , timer . ProgramId ) ) ;
}
var recordPath = RecordingPath ;
var config = GetConfiguration ( ) ;
if ( info . IsMovie )
{
recordPath = Path . Combine ( recordPath , "Movies" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
var customRecordingPath = config . MovieRecordingPath ;
if ( ( string . IsNullOrWhiteSpace ( customRecordingPath ) | | string . Equals ( customRecordingPath , recordPath , StringComparison . OrdinalIgnoreCase ) ) & & config . EnableRecordingSubfolders )
{
recordPath = Path . Combine ( recordPath , "Movies" ) ;
}
var folderName = _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ;
if ( info . ProductionYear . HasValue )
{
folderName + = " (" + info . ProductionYear . Value . ToString ( CultureInfo . InvariantCulture ) + ")" ;
}
recordPath = Path . Combine ( recordPath , folderName ) ;
}
else if ( info . IsSeries )
{
recordPath = Path . Combine ( recordPath , "Series" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
var customRecordingPath = config . SeriesRecordingPath ;
if ( ( string . IsNullOrWhiteSpace ( customRecordingPath ) | | string . Equals ( customRecordingPath , recordPath , StringComparison . OrdinalIgnoreCase ) ) & & config . EnableRecordingSubfolders )
{
recordPath = Path . Combine ( recordPath , "Series" ) ;
}
var folderName = _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ;
var folderNameWithYear = folderName ;
if ( info . ProductionYear . HasValue )
{
folderNameWithYear + = " (" + info . ProductionYear . Value . ToString ( CultureInfo . InvariantCulture ) + ")" ;
}
if ( Directory . Exists ( Path . Combine ( recordPath , folderName ) ) )
{
recordPath = Path . Combine ( recordPath , folderName ) ;
}
else
{
recordPath = Path . Combine ( recordPath , folderNameWithYear ) ;
}
if ( info . SeasonNumber . HasValue )
{
var folderName = string . Format ( "Season {0}" , info . SeasonNumber . Value . ToString ( CultureInfo . InvariantCulture ) ) ;
folderName = string . Format ( "Season {0}" , info . SeasonNumber . Value . ToString ( CultureInfo . InvariantCulture ) ) ;
recordPath = Path . Combine ( recordPath , folderName ) ;
}
}
else if ( info . IsKids )
{
recordPath = Path . Combine ( recordPath , "Kids" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
if ( config . EnableRecordingSubfolders )
{
recordPath = Path . Combine ( recordPath , "Kids" ) ;
}
var folderName = _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ;
if ( info . ProductionYear . HasValue )
{
folderName + = " (" + info . ProductionYear . Value . ToString ( CultureInfo . InvariantCulture ) + ")" ;
}
recordPath = Path . Combine ( recordPath , folderName ) ;
}
else if ( info . IsSports )
{
recordPath = Path . Combine ( recordPath , "Sports" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
if ( config . EnableRecordingSubfolders )
{
recordPath = Path . Combine ( recordPath , "Sports" ) ;
}
recordPath = Path . Combine ( recordPath , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
}
else
{
recordPath = Path . Combine ( recordPath , "Other" , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
if ( config . EnableRecordingSubfolders )
{
recordPath = Path . Combine ( recordPath , "Other" ) ;
}
recordPath = Path . Combine ( recordPath , _fileSystem . GetValidFilename ( info . Name ) . Trim ( ) ) ;
}
var recordingFileName = _fileSystem . GetValidFilename ( RecordingHelper . GetRecordingName ( timer , info ) ) . Trim ( ) + ".ts" ;
recordPath = Path . Combine ( recordPath , recordingFileName ) ;
return Path . Combine ( recordPath , recordingFileName ) ;
}
var recordingId = info . Id . GetMD5 ( ) . ToString ( "N" ) ;
var recording = _recordingProvider . GetAll ( ) . FirstOrDefault ( x = > string . Equals ( x . Id , recordingId , StringComparison . OrdinalIgnoreCase ) ) ;
private async Task RecordStream ( TimerInfo timer , DateTime recordingEndDate , ActiveRecordingInfo activeRecordingInfo , CancellationToken cancellationToken )
{
if ( timer = = null )
{
throw new ArgumentNullException ( "timer" ) ;
}
if ( recording = = null )
ProgramInfo info = null ;
if ( string . IsNullOrWhiteSpace ( timer . ProgramId ) )
{
recording = new RecordingInfo
{
ChannelId = info . ChannelId ,
Id = recordingId ,
StartDate = info . StartDate ,
EndDate = info . EndDate ,
Genres = info . Genres ,
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 ,
EpisodeTitle = info . EpisodeTitle ,
ProgramId = info . Id ,
ImagePath = info . ImagePath ,
ImageUrl = info . ImageUrl ,
OriginalAirDate = info . OriginalAirDate ,
Status = RecordingStatus . Scheduled ,
Overview = info . Overview ,
SeriesTimerId = timer . SeriesTimerId ,
TimerId = timer . Id ,
ShowId = info . ShowId
} ;
_recordingProvider . AddOrUpdate ( recording ) ;
_logger . Info ( "Timer {0} has null programId" , timer . Id ) ;
}
else
{
info = GetProgramInfoFromCache ( timer . ChannelId , timer . ProgramId ) ;
}
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 ) ;
}
if ( info = = null )
{
throw new InvalidOperationException ( string . Format ( "Program with Id {0} not found" , timer . ProgramId ) ) ;
}
var recordPath = GetRecordingPath ( timer , info ) ;
var recordingStatus = RecordingStatus . New ;
try
{
var result = await GetChannelStreamInternal ( timer . ChannelId , null , CancellationToken . None ) . ConfigureAwait ( false ) ;
@ -817,11 +894,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_libraryMonitor . ReportFileSystemChangeBeginning ( recordPath ) ;
recording . Path = recordPath ;
recording . Status = RecordingStatus . InProgress ;
recording . DateLastUpdated = DateTime . UtcNow ;
_recordingProvider . AddOrUpdate ( recording ) ;
var duration = recordingEndDate - DateTime . UtcNow ;
_logger . Info ( "Beginning recording. Will record for {0} minutes." , duration . TotalMinutes . ToString ( CultureInfo . InvariantCulture ) ) ;
@ -846,7 +918,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
await recorder . Record ( mediaStreamInfo , recordPath , duration , onStarted , cancellationToken ) . ConfigureAwait ( false ) ;
recording . Status = RecordingStatus . Completed ;
recording Status = RecordingStatus . Completed ;
_logger . Info ( "Recording completed: {0}" , recordPath ) ;
}
finally
@ -862,12 +934,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
catch ( OperationCanceledException )
{
_logger . Info ( "Recording stopped: {0}" , recordPath ) ;
recording . Status = RecordingStatus . Completed ;
recording Status = RecordingStatus . Completed ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error recording to {0}" , ex , recordPath ) ;
recording . Status = RecordingStatus . Error ;
recording Status = RecordingStatus . Error ;
}
finally
{
@ -875,12 +947,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_activeRecordings . TryRemove ( timer . Id , out removed ) ;
}
recording . DateLastUpdated = DateTime . UtcNow ;
_recordingProvider . AddOrUpdate ( recording ) ;
if ( recording . Status = = RecordingStatus . Completed )
if ( recordingStatus = = RecordingStatus . Completed )
{
OnSuccessfulRecording ( recording ) ;
OnSuccessfulRecording ( info . IsSeries , recordPath ) ;
_timerProvider . Delete ( timer ) ;
}
else if ( DateTime . UtcNow < timer . EndDate )
@ -893,7 +962,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
else
{
_timerProvider . Delete ( timer ) ;
_recordingProvider . Delete ( recording ) ;
}
}
@ -948,11 +1016,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return new DirectRecorder ( _logger , _httpClient , _fileSystem ) ;
}
private async void OnSuccessfulRecording ( RecordingInfo recording )
private async void OnSuccessfulRecording ( bool isSeries , string path )
{
if ( GetConfiguration ( ) . EnableAutoOrganize )
{
if ( record ing. I sSeries)
if ( isSeries)
{
try
{
@ -962,12 +1030,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
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 ) ;
}
var result = await organize . OrganizeEpisodeFile ( path , CancellationToken . None ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
@ -991,18 +1054,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return epgData . FirstOrDefault ( p = > Math . Abs ( startDateTicks - p . StartDate . Ticks ) < = TimeSpan . FromMinutes ( 3 ) . Ticks ) ;
}
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" ) ;
@ -1010,7 +1061,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private async Task UpdateTimersForSeriesTimer ( List < ProgramInfo > epgData , SeriesTimerInfo seriesTimer , bool deleteInvalidTimers )
{
var newTimers = GetTimersForSeries ( seriesTimer , epgData , _recordingProvider . GetAll ( ) ) . ToList ( ) ;
var newTimers = GetTimersForSeries ( seriesTimer , epgData , true ) . ToList ( ) ;
var registration = await GetRegistrationInfo ( "seriesrecordings" ) . ConfigureAwait ( false ) ;
@ -1024,7 +1075,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if ( deleteInvalidTimers )
{
var allTimers = GetTimersForSeries ( seriesTimer , epgData , new List < RecordingInfo > ( ) )
var allTimers = GetTimersForSeries ( seriesTimer , epgData , false )
. Select ( i = > i . Id )
. ToList ( ) ;
@ -1040,7 +1091,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
private IEnumerable < TimerInfo > GetTimersForSeries ( SeriesTimerInfo seriesTimer , IEnumerable < ProgramInfo > allPrograms , IReadOnlyList < RecordingInfo > currentRecordings )
private IEnumerable < TimerInfo > GetTimersForSeries ( SeriesTimerInfo seriesTimer ,
IEnumerable < ProgramInfo > allPrograms ,
bool filterByCurrentRecordings )
{
if ( seriesTimer = = null )
{
@ -1050,23 +1103,71 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
throw new ArgumentNullException ( "allPrograms" ) ;
}
if ( currentRecordings = = null )
{
throw new ArgumentNullException ( "currentRecordings" ) ;
}
// Exclude programs that have already ended
allPrograms = allPrograms . Where ( i = > i . EndDate > DateTime . UtcNow & & i . StartDate > DateTime . UtcNow ) ;
allPrograms = GetProgramsForSeries ( seriesTimer , allPrograms ) ;
var recordingShowIds = currentRecordings . Select ( i = > i . ProgramId ) . Where ( i = > ! string . IsNullOrWhiteSpace ( i ) ) . ToList ( ) ;
allPrograms = allPrograms . Where ( i = > ! recordingShowIds . Contains ( i . Id , StringComparer . OrdinalIgnoreCase ) ) ;
if ( filterByCurrentRecordings )
{
allPrograms = allPrograms . Where ( i = > ! IsProgramAlreadyInLibrary ( i ) ) ;
}
return allPrograms . Select ( i = > RecordingHelper . CreateTimer ( i , seriesTimer ) ) ;
}
private bool IsProgramAlreadyInLibrary ( ProgramInfo program )
{
if ( ( program . EpisodeNumber . HasValue & & program . SeasonNumber . HasValue ) | | ! string . IsNullOrWhiteSpace ( program . EpisodeTitle ) )
{
var seriesIds = _libraryManager . GetItemIds ( new InternalItemsQuery
{
IncludeItemTypes = new [ ] { typeof ( Series ) . Name } ,
Name = program . Name
} ) . Select ( i = > i . ToString ( "N" ) ) . ToArray ( ) ;
if ( seriesIds . Length = = 0 )
{
return false ;
}
if ( program . EpisodeNumber . HasValue & & program . SeasonNumber . HasValue )
{
var result = _libraryManager . GetItemsResult ( new InternalItemsQuery
{
IncludeItemTypes = new [ ] { typeof ( Episode ) . Name } ,
ParentIndexNumber = program . SeasonNumber . Value ,
IndexNumber = program . EpisodeNumber . Value ,
AncestorIds = seriesIds
} ) ;
if ( result . TotalRecordCount > 0 )
{
return true ;
}
}
if ( ! string . IsNullOrWhiteSpace ( program . EpisodeTitle ) )
{
var result = _libraryManager . GetItemsResult ( new InternalItemsQuery
{
IncludeItemTypes = new [ ] { typeof ( Episode ) . Name } ,
Name = program . EpisodeTitle ,
AncestorIds = seriesIds
} ) ;
if ( result . TotalRecordCount > 0 )
{
return true ;
}
}
}
return false ;
}
private IEnumerable < ProgramInfo > GetProgramsForSeries ( SeriesTimerInfo seriesTimer , IEnumerable < ProgramInfo > allPrograms )
{
if ( ! seriesTimer . RecordAnyTime )
@ -1151,6 +1252,47 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
} ) ;
}
public List < VirtualFolderInfo > GetRecordingFolders ( )
{
var list = new List < VirtualFolderInfo > ( ) ;
var defaultFolder = RecordingPath ;
var defaultName = "Recordings" ;
if ( Directory . Exists ( defaultFolder ) )
{
list . Add ( new VirtualFolderInfo
{
Locations = new List < string > { defaultFolder } ,
Name = defaultName
} ) ;
}
var customPath = GetConfiguration ( ) . MovieRecordingPath ;
if ( ( ! string . IsNullOrWhiteSpace ( customPath ) & & ! string . Equals ( customPath , defaultFolder , StringComparison . OrdinalIgnoreCase ) ) & & Directory . Exists ( customPath ) )
{
list . Add ( new VirtualFolderInfo
{
Locations = new List < string > { customPath } ,
Name = "Recorded Movies" ,
CollectionType = CollectionType . Movies
} ) ;
}
customPath = GetConfiguration ( ) . SeriesRecordingPath ;
if ( ( ! string . IsNullOrWhiteSpace ( customPath ) & & ! string . Equals ( customPath , defaultFolder , StringComparison . OrdinalIgnoreCase ) ) & & Directory . Exists ( customPath ) )
{
list . Add ( new VirtualFolderInfo
{
Locations = new List < string > { customPath } ,
Name = "Recorded Series" ,
CollectionType = CollectionType . TvShows
} ) ;
}
return list ;
}
class ActiveRecordingInfo
{
public string Path { get ; set ; }