update recording saving

This commit is contained in:
Luke Pulverenti 2016-05-04 16:50:47 -04:00
parent 68ae463381
commit 33c002684e
7 changed files with 404 additions and 282 deletions

View File

@ -1,54 +0,0 @@
using MediaBrowser.Controller;
using System;
using System.IO;
using System.Linq;
using CommonIO;
namespace MediaBrowser.Api.Library
{
/// <summary>
/// Class LibraryHelpers
/// </summary>
public static class LibraryHelpers
{
/// <summary>
/// The shortcut file extension
/// </summary>
private const string ShortcutFileExtension = ".mblink";
/// <summary>
/// The shortcut file search
/// </summary>
private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
/// <summary>
/// Deletes a shortcut from within a virtual folder, within either the default view or a user view
/// </summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="virtualFolderName">Name of the virtual folder.</param>
/// <param name="mediaPath">The media path.</param>
/// <param name="appPaths">The app paths.</param>
/// <exception cref="System.IO.DirectoryNotFoundException">The media folder does not exist</exception>
public static void RemoveMediaPath(IFileSystem fileSystem, string virtualFolderName, string mediaPath, IServerApplicationPaths appPaths)
{
if (string.IsNullOrWhiteSpace(mediaPath))
{
throw new ArgumentNullException("mediaPath");
}
var rootFolderPath = appPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, virtualFolderName);
if (!fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
}
var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(shortcut))
{
fileSystem.DeleteFile(shortcut);
}
}
}
}

View File

@ -268,46 +268,7 @@ namespace MediaBrowser.Api.Library
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
public void Delete(RemoveVirtualFolder request) public void Delete(RemoveVirtualFolder request)
{ {
if (string.IsNullOrWhiteSpace(request.Name)) _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary);
{
throw new ArgumentNullException("request");
}
var rootFolderPath = _appPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, request.Name);
if (!_fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException("The media folder does not exist");
}
_libraryMonitor.Stop();
try
{
_fileSystem.DeleteDirectory(path, true);
}
finally
{
Task.Run(() =>
{
// No need to start if scanning the library because it will handle it
if (request.RefreshLibrary)
{
_libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
}
else
{
// Need to add a delay here or directory watchers may still pick up the changes
var task = Task.Delay(1000);
// Have to block here to allow exceptions to bubble
Task.WaitAll(task);
_libraryMonitor.Start();
}
});
}
} }
/// <summary> /// <summary>
@ -364,7 +325,7 @@ namespace MediaBrowser.Api.Library
try try
{ {
LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths); _libraryManager.RemoveMediaPath(request.Name, request.Path);
} }
finally finally
{ {

View File

@ -129,7 +129,6 @@
<Compile Include="ItemUpdateService.cs" /> <Compile Include="ItemUpdateService.cs" />
<Compile Include="Library\LibraryService.cs" /> <Compile Include="Library\LibraryService.cs" />
<Compile Include="Library\FileOrganizationService.cs" /> <Compile Include="Library\FileOrganizationService.cs" />
<Compile Include="Library\LibraryHelpers.cs" />
<Compile Include="Library\LibraryStructureService.cs" /> <Compile Include="Library\LibraryStructureService.cs" />
<Compile Include="LiveTv\LiveTvService.cs" /> <Compile Include="LiveTv\LiveTvService.cs" />
<Compile Include="LocalizationService.cs" /> <Compile Include="LocalizationService.cs" />

View File

@ -571,6 +571,8 @@ namespace MediaBrowser.Controller.Library
bool IgnoreFile(FileSystemMetadata file, BaseItem parent); bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary); void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary);
void RemoveVirtualFolder(string name, bool refreshLibrary);
void AddMediaPath(string virtualFolderName, string path); void AddMediaPath(string virtualFolderName, string path);
void RemoveMediaPath(string virtualFolderName, string path);
} }
} }

View File

@ -7,8 +7,11 @@ namespace MediaBrowser.Model.LiveTv
public int? GuideDays { get; set; } public int? GuideDays { get; set; }
public bool EnableMovieProviders { get; set; } public bool EnableMovieProviders { get; set; }
public string RecordingPath { get; set; } public string RecordingPath { get; set; }
public string MovieRecordingPath { get; set; }
public string SeriesRecordingPath { get; set; }
public bool EnableAutoOrganize { get; set; } public bool EnableAutoOrganize { get; set; }
public bool EnableRecordingEncoding { get; set; } public bool EnableRecordingEncoding { get; set; }
public bool EnableRecordingSubfolders { get; set; }
public bool EnableOriginalAudioWithEncodedRecordings { get; set; } public bool EnableOriginalAudioWithEncodedRecordings { get; set; }
public List<TunerHostInfo> TunerHosts { get; set; } public List<TunerHostInfo> TunerHosts { get; set; }
@ -20,6 +23,7 @@ namespace MediaBrowser.Model.LiveTv
public LiveTvOptions() public LiveTvOptions()
{ {
EnableMovieProviders = true; EnableMovieProviders = true;
EnableRecordingSubfolders = true;
TunerHosts = new List<TunerHostInfo>(); TunerHosts = new List<TunerHostInfo>();
ListingProviders = new List<ListingsProviderInfo>(); ListingProviders = new List<ListingsProviderInfo>();
} }

View File

@ -2640,7 +2640,52 @@ namespace MediaBrowser.Server.Implementations.Library
} }
} }
public void RemoveVirtualFolder(string name, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException("name");
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, name);
if (!_fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException("The media folder does not exist");
}
_libraryMonitorFactory().Stop();
try
{
_fileSystem.DeleteDirectory(path, true);
}
finally
{
Task.Run(() =>
{
// No need to start if scanning the library because it will handle it
if (refreshLibrary)
{
ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
}
else
{
// Need to add a delay here or directory watchers may still pick up the changes
var task = Task.Delay(1000);
// Have to block here to allow exceptions to bubble
Task.WaitAll(task);
_libraryMonitorFactory().Start();
}
});
}
}
private const string ShortcutFileExtension = ".mblink"; private const string ShortcutFileExtension = ".mblink";
private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
public void AddMediaPath(string virtualFolderName, string path) public void AddMediaPath(string virtualFolderName, string path)
{ {
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
@ -2668,5 +2713,28 @@ namespace MediaBrowser.Server.Implementations.Library
_fileSystem.CreateShortcut(lnk, path); _fileSystem.CreateShortcut(lnk, path);
} }
public void RemoveMediaPath(string virtualFolderName, string mediaPath)
{
if (string.IsNullOrWhiteSpace(mediaPath))
{
throw new ArgumentNullException("mediaPath");
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, virtualFolderName);
if (!_fileSystem.DirectoryExists(path))
{
throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
}
var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(shortcut))
{
_fileSystem.DeleteFile(shortcut);
}
}
} }
} }

View File

@ -26,7 +26,10 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Power; using MediaBrowser.Controller.Power;
using Microsoft.Win32; using Microsoft.Win32;
@ -40,7 +43,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly ItemDataProvider<RecordingInfo> _recordingProvider;
private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider; private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider;
private readonly TimerManager _timerProvider; private readonly TimerManager _timerProvider;
@ -56,6 +58,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public static EmbyTV Current; 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) 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; Current = this;
@ -74,10 +82,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_liveTvManager = (LiveTvManager)liveTvManager; _liveTvManager = (LiveTvManager)liveTvManager;
_jsonSerializer = jsonSerializer; _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")); _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger); _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired; _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() public void Start()
@ -85,6 +102,95 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_timerProvider.RestartTimers(); _timerProvider.RestartTimers();
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; 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) 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 public string Name
{ {
get { return "Emby"; } get { return "Emby"; }
@ -114,6 +213,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv"); } 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 public string HomePageUrl
{ {
get { return "http://emby.media"; } get { return "http://emby.media"; }
@ -280,49 +399,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return Task.FromResult(true); 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)); return Task.FromResult(true);
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);
}
} }
public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken) 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) public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
{ {
var recordings = _recordingProvider.GetAll().ToList(); return new List<RecordingInfo>();
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;
} }
public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken) public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
@ -695,6 +752,92 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
} }
} }
private string GetRecordingPath(TimerInfo timer, ProgramInfo info)
{
var recordPath = RecordingPath;
var config = GetConfiguration();
if (info.IsMovie)
{
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)
{
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)
{
folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
recordPath = Path.Combine(recordPath, folderName);
}
}
else if (info.IsKids)
{
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)
{
if (config.EnableRecordingSubfolders)
{
recordPath = Path.Combine(recordPath, "Sports");
}
recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
}
else
{
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";
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)
@ -724,74 +867,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId)); throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId));
} }
var recordPath = RecordingPath; var recordPath = GetRecordingPath(timer, info);
var recordingStatus = RecordingStatus.New;
if (info.IsMovie)
{
recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name).Trim());
}
else if (info.IsSeries)
{
recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name).Trim());
if (info.SeasonNumber.HasValue)
{
var 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());
}
else if (info.IsSports)
{
recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name).Trim());
}
else
{
recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name).Trim());
}
var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
recordPath = Path.Combine(recordPath, recordingFileName);
var recordingId = info.Id.GetMD5().ToString("N");
var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.Id, recordingId, StringComparison.OrdinalIgnoreCase));
if (recording == null)
{
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);
}
try try
{ {
@ -817,11 +894,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_libraryMonitor.ReportFileSystemChangeBeginning(recordPath); _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
recording.Path = recordPath;
recording.Status = RecordingStatus.InProgress;
recording.DateLastUpdated = DateTime.UtcNow;
_recordingProvider.AddOrUpdate(recording);
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));
@ -846,7 +918,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false); await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
recording.Status = RecordingStatus.Completed; recordingStatus = RecordingStatus.Completed;
_logger.Info("Recording completed: {0}", recordPath); _logger.Info("Recording completed: {0}", recordPath);
} }
finally finally
@ -862,12 +934,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
_logger.Info("Recording stopped: {0}", recordPath); _logger.Info("Recording stopped: {0}", recordPath);
recording.Status = RecordingStatus.Completed; recordingStatus = RecordingStatus.Completed;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.ErrorException("Error recording to {0}", ex, recordPath); _logger.ErrorException("Error recording to {0}", ex, recordPath);
recording.Status = RecordingStatus.Error; recordingStatus = RecordingStatus.Error;
} }
finally finally
{ {
@ -875,12 +947,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_activeRecordings.TryRemove(timer.Id, out removed); _activeRecordings.TryRemove(timer.Id, out removed);
} }
recording.DateLastUpdated = DateTime.UtcNow; if (recordingStatus == RecordingStatus.Completed)
_recordingProvider.AddOrUpdate(recording);
if (recording.Status == RecordingStatus.Completed)
{ {
OnSuccessfulRecording(recording); OnSuccessfulRecording(info.IsSeries, recordPath);
_timerProvider.Delete(timer); _timerProvider.Delete(timer);
} }
else if (DateTime.UtcNow < timer.EndDate) else if (DateTime.UtcNow < timer.EndDate)
@ -893,7 +962,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
else else
{ {
_timerProvider.Delete(timer); _timerProvider.Delete(timer);
_recordingProvider.Delete(recording);
} }
} }
@ -948,11 +1016,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return new DirectRecorder(_logger, _httpClient, _fileSystem); return new DirectRecorder(_logger, _httpClient, _fileSystem);
} }
private async void OnSuccessfulRecording(RecordingInfo recording) private async void OnSuccessfulRecording(bool isSeries, string path)
{ {
if (GetConfiguration().EnableAutoOrganize) if (GetConfiguration().EnableAutoOrganize)
{ {
if (recording.IsSeries) if (isSeries)
{ {
try try
{ {
@ -962,12 +1030,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
var result = await organize.OrganizeEpisodeFile(recording.Path, CancellationToken.None).ConfigureAwait(false); var result = await organize.OrganizeEpisodeFile(path, CancellationToken.None).ConfigureAwait(false);
if (result.Status == FileSortingStatus.Success)
{
_recordingProvider.Delete(recording);
}
} }
catch (Exception ex) 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); 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() private LiveTvOptions GetConfiguration()
{ {
return _config.GetConfiguration<LiveTvOptions>("livetv"); 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) 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); var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
@ -1024,7 +1075,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (deleteInvalidTimers) if (deleteInvalidTimers)
{ {
var allTimers = GetTimersForSeries(seriesTimer, epgData, new List<RecordingInfo>()) var allTimers = GetTimersForSeries(seriesTimer, epgData, false)
.Select(i => i.Id) .Select(i => i.Id)
.ToList(); .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) if (seriesTimer == null)
{ {
@ -1050,23 +1103,71 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
throw new ArgumentNullException("allPrograms"); throw new ArgumentNullException("allPrograms");
} }
if (currentRecordings == null)
{
throw new ArgumentNullException("currentRecordings");
}
// Exclude programs that have already ended // Exclude programs that have already ended
allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow); allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
var recordingShowIds = currentRecordings.Select(i => i.ProgramId).Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); if (filterByCurrentRecordings)
{
allPrograms = allPrograms.Where(i => !recordingShowIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase)); 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)
{
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) private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
{ {
if (!seriesTimer.RecordAnyTime) 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 class ActiveRecordingInfo
{ {
public string Path { get; set; } public string Path { get; set; }