diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
index b5943e17b0..62d285072c 100644
--- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
@@ -488,20 +488,17 @@ namespace Emby.Common.Implementations.IO
}
var temp1 = Path.GetTempFileName();
- var temp2 = Path.GetTempFileName();
// Copying over will fail against hidden files
RemoveHiddenAttribute(file1);
RemoveHiddenAttribute(file2);
CopyFile(file1, temp1, true);
- CopyFile(file2, temp2, true);
+ CopyFile(file2, file1, true);
CopyFile(temp1, file2, true);
- CopyFile(temp2, file1, true);
DeleteFile(temp1);
- DeleteFile(temp2);
}
///
diff --git a/Emby.Common.Implementations/Logging/NlogManager.cs b/Emby.Common.Implementations/Logging/NlogManager.cs
index e38b87bd13..f7b723e8bc 100644
--- a/Emby.Common.Implementations/Logging/NlogManager.cs
+++ b/Emby.Common.Implementations/Logging/NlogManager.cs
@@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Linq;
+using System.Xml;
using NLog;
using NLog.Config;
+using NLog.Filters;
using NLog.Targets;
using NLog.Targets.Wrappers;
using MediaBrowser.Model.Logging;
@@ -14,20 +16,35 @@ namespace Emby.Common.Implementations.Logging
///
public class NlogManager : ILogManager
{
- ///
- /// Occurs when [logger loaded].
- ///
- public event EventHandler LoggerLoaded;
+ #region Private Fields
+
+ private LogSeverity _severity = LogSeverity.Debug;
+
///
/// Gets or sets the log directory.
///
/// The log directory.
- private string LogDirectory { get; set; }
+ private readonly string LogDirectory;
+
///
/// Gets or sets the log file prefix.
///
/// The log file prefix.
- private string LogFilePrefix { get; set; }
+ private readonly string LogFilePrefix;
+
+ #endregion
+
+ #region Event Declarations
+
+ ///
+ /// Occurs when [logger loaded].
+ ///
+ public event EventHandler LoggerLoaded;
+
+ #endregion
+
+ #region Public Properties
+
///
/// Gets the log file path.
///
@@ -40,28 +57,25 @@ namespace Emby.Common.Implementations.Logging
/// The exception message prefix.
public string ExceptionMessagePrefix { get; set; }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The log directory.
- /// The log file name prefix.
- public NlogManager(string logDirectory, string logFileNamePrefix)
- {
- LogDirectory = logDirectory;
- LogFilePrefix = logFileNamePrefix;
+ public string NLogConfigurationFilePath { get; set; }
- LogManager.Configuration = new LoggingConfiguration ();
- }
-
- private LogSeverity _severity = LogSeverity.Debug;
public LogSeverity LogSeverity
{
+
get
{
return _severity;
}
+
set
{
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "SET LogSeverity, _severity = [{0}], value = [{1}]",
+ _severity.ToString(),
+ value.ToString()
+ ));
+
var changed = _severity != value;
_severity = value;
@@ -70,11 +84,124 @@ namespace Emby.Common.Implementations.Logging
{
UpdateLogLevel(value);
}
+
+ }
+ }
+
+ #endregion
+
+ #region Constructor(s)
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The log directory.
+ /// The log file name prefix.
+ public NlogManager(string logDirectory, string logFileNamePrefix)
+ {
+ DebugFileWriter(
+ logDirectory, String.Format(
+ "NlogManager constructor called, logDirectory is [{0}], logFileNamePrefix is [{1}], _severity is [{2}].",
+ logDirectory,
+ logFileNamePrefix,
+ _severity.ToString()
+ ));
+
+ LogDirectory = logDirectory;
+ LogFilePrefix = logFileNamePrefix;
+
+ LogManager.Configuration = new LoggingConfiguration();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The log directory.
+ /// The log file name prefix.
+ public NlogManager(string logDirectory, string logFileNamePrefix, LogSeverity initialSeverity) : this(logDirectory, logFileNamePrefix)
+ {
+ _severity = initialSeverity;
+
+ DebugFileWriter(
+ logDirectory, String.Format(
+ "NlogManager constructor called, logDirectory is [{0}], logFileNamePrefix is [{1}], _severity is [{2}].",
+ logDirectory,
+ logFileNamePrefix,
+ _severity.ToString()
+ ));
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Adds the file target.
+ ///
+ /// The path.
+ /// The level.
+ private void AddFileTarget(string path, LogSeverity level)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "AddFileTarget called, path = [{0}], level = [{1}].",
+ path,
+ level.ToString()
+ ));
+
+ RemoveTarget("ApplicationLogFileWrapper");
+
+ var wrapper = new AsyncTargetWrapper();
+ wrapper.Name = "ApplicationLogFileWrapper";
+
+ var logFile = new FileTarget
+ {
+ FileName = path,
+ Layout = "${longdate} ${level} ${logger}: ${message}"
+ };
+
+ logFile.Name = "ApplicationLogFile";
+
+ wrapper.WrappedTarget = logFile;
+
+ AddLogTarget(wrapper, level);
+
+ }
+
+ ///
+ /// Gets the log level.
+ ///
+ /// The severity.
+ /// LogLevel.
+ /// Unrecognized LogSeverity
+ private LogLevel GetLogLevel(LogSeverity severity)
+ {
+ switch (severity)
+ {
+ case LogSeverity.Debug:
+ return LogLevel.Debug;
+ case LogSeverity.Error:
+ return LogLevel.Error;
+ case LogSeverity.Fatal:
+ return LogLevel.Fatal;
+ case LogSeverity.Info:
+ return LogLevel.Info;
+ case LogSeverity.Warn:
+ return LogLevel.Warn;
+ default:
+ throw new ArgumentException("Unrecognized LogSeverity");
}
}
private void UpdateLogLevel(LogSeverity newLevel)
{
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "UpdateLogLevel called, newLevel = [{0}].",
+ newLevel.ToString()
+ ));
+
var level = GetLogLevel(newLevel);
var rules = LogManager.Configuration.LoggingRules;
@@ -95,29 +222,117 @@ namespace Emby.Common.Implementations.Logging
}
}
- ///
- /// Adds the file target.
- ///
- /// The path.
- /// The level.
- private void AddFileTarget(string path, LogSeverity level)
+ private void AddCustomFilters(string defaultLoggerNamePattern, LoggingRule defaultRule)
{
- RemoveTarget("ApplicationLogFileWrapper");
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "AddCustomFilters called, defaultLoggerNamePattern = [{0}], defaultRule.LoggerNamePattern = [{1}].",
+ defaultLoggerNamePattern,
+ defaultRule.LoggerNamePattern
+ ));
+
+ try
+ {
+ var customConfig = new NLog.Config.XmlLoggingConfiguration(NLogConfigurationFilePath);
- var wrapper = new AsyncTargetWrapper ();
- wrapper.Name = "ApplicationLogFileWrapper";
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Custom Configuration Loaded, Rule Count = [{0}].",
+ customConfig.LoggingRules.Count.ToString()
+ ));
- var logFile = new FileTarget
+ foreach (var customRule in customConfig.LoggingRules)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Read Custom Rule, LoggerNamePattern = [{0}], Targets = [{1}].",
+ customRule.LoggerNamePattern,
+ string.Join(",", customRule.Targets.Select(x => x.Name).ToList())
+ ));
+
+ if (customRule.LoggerNamePattern.Equals(defaultLoggerNamePattern))
+ {
+
+ if (customRule.Targets.Any((arg) => arg.Name.Equals(defaultRule.Targets.First().Name)))
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Custom rule filters can be applied to this target, Filter Count = [{0}].",
+ customRule.Filters.Count.ToString()
+ ));
+
+ foreach (ConditionBasedFilter customFilter in customRule.Filters)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Read Custom Filter, Filter = [{0}], Action = [{1}], Type = [{2}].",
+ customFilter.Condition.ToString(),
+ customFilter.Action.ToString(),
+ customFilter.GetType().ToString()
+ ));
+
+ defaultRule.Filters.Add(customFilter);
+
+ }
+ }
+ else
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Ignoring custom rule as [Target] does not match."
+ ));
+
+ }
+
+ }
+ else
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Ignoring custom rule as [LoggerNamePattern] does not match."
+ ));
+
+ }
+ }
+ }
+ catch (Exception ex)
{
- FileName = path,
- Layout = "${longdate} ${level} ${logger}: ${message}"
- };
+ // Intentionally do nothing, prevent issues affecting normal execution.
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Exception in AddCustomFilters, ex.Message = [{0}].",
+ ex.Message
+ )
+ );
- logFile.Name = "ApplicationLogFile";
+ }
+ }
- wrapper.WrappedTarget = logFile;
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Gets the logger.
+ ///
+ /// The name.
+ /// ILogger.
+ public MediaBrowser.Model.Logging.ILogger GetLogger(string name)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "GetLogger called, name = [{0}].",
+ name
+ ));
+
+ return new NLogger(name, this);
- AddLogTarget(wrapper, level);
}
///
@@ -127,13 +342,26 @@ namespace Emby.Common.Implementations.Logging
/// The level.
public void AddLogTarget(Target target, LogSeverity level)
{
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "AddLogTarget called, target.Name = [{0}], level = [{1}].",
+ target.Name,
+ level.ToString()
+ ));
+
+ string loggerNamePattern = "*";
var config = LogManager.Configuration;
+ var rule = new LoggingRule(loggerNamePattern, GetLogLevel(level), target);
+
config.AddTarget(target.Name, target);
- var rule = new LoggingRule("*", GetLogLevel(level), target);
+ AddCustomFilters(loggerNamePattern, rule);
+
config.LoggingRules.Add(rule);
LogManager.Configuration = config;
+
}
///
@@ -142,6 +370,13 @@ namespace Emby.Common.Implementations.Logging
/// The name.
public void RemoveTarget(string name)
{
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "RemoveTarget called, name = [{0}].",
+ name
+ ));
+
var config = LogManager.Configuration;
var target = config.FindTargetByName(name);
@@ -165,50 +400,70 @@ namespace Emby.Common.Implementations.Logging
}
}
- ///
- /// Gets the logger.
- ///
- /// The name.
- /// ILogger.
- public MediaBrowser.Model.Logging.ILogger GetLogger(string name)
+ public void AddConsoleOutput()
{
- return new NLogger(name, this);
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "AddConsoleOutput called."
+ ));
+
+ RemoveTarget("ConsoleTargetWrapper");
+
+ var wrapper = new AsyncTargetWrapper();
+ wrapper.Name = "ConsoleTargetWrapper";
+
+ var target = new ConsoleTarget()
+ {
+ Layout = "${level}, ${logger}, ${message}",
+ Error = false
+ };
+
+ target.Name = "ConsoleTarget";
+
+ wrapper.WrappedTarget = target;
+
+ AddLogTarget(wrapper, LogSeverity);
+
+ }
+
+ public void RemoveConsoleOutput()
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "RemoveConsoleOutput called."
+ ));
+
+ RemoveTarget("ConsoleTargetWrapper");
+
}
///
- /// Gets the log level.
+ /// Reloads the logger, maintaining the current log level.
///
- /// The severity.
- /// LogLevel.
- /// Unrecognized LogSeverity
- private LogLevel GetLogLevel(LogSeverity severity)
+ public void ReloadLogger()
{
- switch (severity)
- {
- case LogSeverity.Debug:
- return LogLevel.Debug;
- case LogSeverity.Error:
- return LogLevel.Error;
- case LogSeverity.Fatal:
- return LogLevel.Fatal;
- case LogSeverity.Info:
- return LogLevel.Info;
- case LogSeverity.Warn:
- return LogLevel.Warn;
- default:
- throw new ArgumentException("Unrecognized LogSeverity");
- }
+ ReloadLogger(LogSeverity);
}
///
- /// Reloads the logger.
+ /// Reloads the logger, using the specified logging level.
///
/// The level.
public void ReloadLogger(LogSeverity level)
{
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "ReloadLogger called, level = [{0}], LogFilePath (existing) = [{1}].",
+ level.ToString(),
+ LogFilePath
+ ));
+
LogFilePath = Path.Combine(LogDirectory, LogFilePrefix + "-" + decimal.Floor(DateTime.Now.Ticks / 10000000) + ".txt");
- Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
+ Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
AddFileTarget(LogFilePath, level);
@@ -218,7 +473,14 @@ namespace Emby.Common.Implementations.Logging
{
try
{
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "ReloadLogger called, raised event LoggerLoaded."
+ ));
+
LoggerLoaded(this, EventArgs.Empty);
+
}
catch (Exception ex)
{
@@ -232,33 +494,51 @@ namespace Emby.Common.Implementations.Logging
///
public void Flush()
{
- LogManager.Flush();
- }
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Flush called."
+ ));
- public void AddConsoleOutput()
- {
- RemoveTarget("ConsoleTargetWrapper");
+ LogManager.Flush();
- var wrapper = new AsyncTargetWrapper ();
- wrapper.Name = "ConsoleTargetWrapper";
+ }
- var target = new ConsoleTarget()
- {
- Layout = "${level}, ${logger}, ${message}",
- Error = false
- };
+ #endregion
- target.Name = "ConsoleTarget";
+ #region Conditional Debug Methods
- wrapper.WrappedTarget = target;
+ ///
+ /// DEBUG: Standalone method to write out debug to assist with logger development/troubleshooting.
+ ///
+ /// - The output file will be written to the server's log directory.
+ /// - Calls to the method are safe and will never throw any exceptions.
+ /// - Method calls will be omitted unless the library is compiled with DEBUG defined.
+ ///
+ ///
+ private static void DebugFileWriter(string logDirectory, string message)
+ {
+#if DEBUG
+ try
+ {
- AddLogTarget(wrapper, LogSeverity);
- }
+ System.IO.File.AppendAllText(
+ Path.Combine(logDirectory, "NlogManager.txt"),
+ String.Format(
+ "{0} : {1}{2}",
+ System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
+ message,
+ System.Environment.NewLine
+ )
+ );
- public void RemoveConsoleOutput()
- {
- RemoveTarget("ConsoleTargetWrapper");
+ }
+ catch (Exception ex)
+ {
+ // Intentionally do nothing, prevent issues affecting normal execution.
+ }
+#endif
}
+ #endregion
}
-}
+}
\ No newline at end of file
diff --git a/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs
index 11e275940d..b75de2ff4f 100644
--- a/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs
@@ -238,7 +238,7 @@ namespace Emby.Server.Core.EntryPoints
}
catch (Exception ex)
{
- _logger.ErrorException("Error creating port map", ex);
+ _logger.Error("Error creating port map: " + ex.Message);
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index a5b19ff524..84f802d761 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -30,6 +30,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Priority = seriesTimer.Priority,
Name = parent.Name,
Overview = parent.Overview,
+ SeriesId = parent.SeriesId,
SeriesTimerId = seriesTimer.Id,
ShowId = parent.ShowId
};
@@ -63,6 +64,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
timerInfo.ShortOverview = programInfo.ShortOverview;
timerInfo.OfficialRating = programInfo.OfficialRating;
timerInfo.IsRepeat = programInfo.IsRepeat;
+ timerInfo.SeriesId = programInfo.SeriesId;
}
public static string GetRecordingName(TimerInfo info)
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 5fa3995e6a..7c1de251ca 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -14,6 +14,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.LiveTv
{
@@ -80,6 +81,11 @@ namespace Emby.Server.Implementations.LiveTv
}
dto.ProgramInfo.SeriesTimerId = dto.SeriesTimerId;
+
+ if (!string.IsNullOrWhiteSpace(info.SeriesTimerId))
+ {
+ FillImages(dto.ProgramInfo, info.Name, info.SeriesId);
+ }
}
if (channel != null)
@@ -131,17 +137,73 @@ namespace Emby.Server.Implementations.LiveTv
dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days);
- FillImages(dto, info);
+ FillImages(dto, info.Name, info.SeriesId);
return dto;
}
- private void FillImages(SeriesTimerInfoDto dto, SeriesTimerInfo info)
+ private void FillImages(BaseItemDto dto, string seriesName, string programSeriesId)
{
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(Series).Name },
- Name = info.Name,
+ Name = seriesName,
+ Limit = 1,
+ ImageTypes = new ImageType[] { ImageType.Thumb }
+
+ }).FirstOrDefault();
+
+ if (librarySeries != null)
+ {
+ var image = librarySeries.GetImageInfo(ImageType.Thumb, 0);
+ if (image != null)
+ {
+ try
+ {
+ dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
+ dto.ParentThumbItemId = librarySeries.Id.ToString("N");
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(programSeriesId))
+ {
+ var program = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
+ ExternalSeriesId = programSeriesId,
+ Limit = 1,
+ ImageTypes = new ImageType[] { ImageType.Primary }
+
+ }).FirstOrDefault();
+
+ if (program != null)
+ {
+ var image = program.GetImageInfo(ImageType.Primary, 0);
+ if (image != null)
+ {
+ try
+ {
+ dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
+ dto.ParentPrimaryImageItemId = program.Id.ToString("N");
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }
+ }
+ }
+
+ private void FillImages(SeriesTimerInfoDto dto, string seriesName, string programSeriesId)
+ {
+ var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new string[] { typeof(Series).Name },
+ Name = seriesName,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb }
@@ -163,12 +225,12 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- if (!string.IsNullOrWhiteSpace(info.SeriesId))
+ if (!string.IsNullOrWhiteSpace(programSeriesId))
{
var program = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
- ExternalSeriesId = info.SeriesId,
+ ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary }
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 52bf09284b..0420900c55 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -42,7 +42,8 @@ namespace Emby.Server.Implementations.Updates
///
private ConcurrentBag CompletedInstallationsInternal { get; set; }
- public IEnumerable CompletedInstallations {
+ public IEnumerable CompletedInstallations
+ {
get { return CompletedInstallationsInternal; }
}
@@ -163,8 +164,8 @@ namespace Emby.Server.Implementations.Updates
{
var data = new Dictionary
{
- { "key", _securityManager.SupporterKey },
- { "mac", _applicationHost.SystemId },
+ { "key", _securityManager.SupporterKey },
+ { "mac", _applicationHost.SystemId },
{ "systemid", _applicationHost.SystemId }
};
@@ -656,6 +657,8 @@ namespace Emby.Server.Implementations.Updates
// Remove it the quick way for now
_applicationHost.RemovePlugin(plugin);
+ _logger.Info("Deleting plugin file {0}", plugin.AssemblyFilePath);
+
_fileSystem.DeleteFile(plugin.AssemblyFilePath);
OnPluginUninstalled(plugin);
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index a84e9a5d23..943c9d8822 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -103,6 +103,16 @@ namespace MediaBrowser.Controller.Entities
}
}
+ public override bool CanDelete()
+ {
+ if (IsRoot)
+ {
+ return false;
+ }
+
+ return base.CanDelete();
+ }
+
public override bool RequiresRefresh()
{
var baseResult = base.RequiresRefresh();
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
index ee8dd5d3a7..10ed95fe5e 100644
--- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -46,6 +46,8 @@ namespace MediaBrowser.Controller.LiveTv
///
public string Overview { get; set; }
+ public string SeriesId { get; set; }
+
///
/// The start date of the recording, in UTC.
///