浏览代码

updated live tv + nuget

Luke Pulverenti 11 年之前
父节点
当前提交
01e65c93ee
共有 39 个文件被更改,包括 997 次插入421 次删除
  1. 2 0
      MediaBrowser.Api/Images/ImageService.cs
  2. 67 9
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  3. 7 26
      MediaBrowser.Common.Implementations/BaseApplicationPaths.cs
  4. 38 1
      MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
  5. 2 0
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  6. 4 4
      MediaBrowser.Controller/IServerApplicationPaths.cs
  7. 46 10
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  8. 9 2
      MediaBrowser.Controller/LiveTv/ILiveTvService.cs
  9. 0 6
      MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs
  10. 27 15
      MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
  11. 22 86
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  12. 3 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  13. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  14. 7 1
      MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
  15. 7 0
      MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
  16. 7 0
      MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
  17. 10 0
      MediaBrowser.Model/LiveTv/RecordingQuery.cs
  18. 8 1
      MediaBrowser.Model/LiveTv/RecordingStatus.cs
  19. 120 0
      MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
  20. 12 0
      MediaBrowser.Model/LiveTv/TimerInfoDto.cs
  21. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  22. 6 0
      MediaBrowser.Model/System/SystemInfo.cs
  23. 2 1
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  24. 1 9
      MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
  25. 20 11
      MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
  26. 27 11
      MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
  27. 6 2
      MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
  28. 324 0
      MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
  29. 84 212
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  30. 2 1
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  31. 2 1
      MediaBrowser.ServerApplication/ApplicationHost.cs
  32. 2 0
      MediaBrowser.ServerApplication/Native/ServerAuthorization.cs
  33. 18 1
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  34. 94 4
      MediaBrowser.WebDashboard/ApiClient.js
  35. 1 1
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  36. 1 1
      MediaBrowser.WebDashboard/packages.config
  37. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  38. 1 1
      Nuget/MediaBrowser.Common.nuspec
  39. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 2 - 0
MediaBrowser.Api/Images/ImageService.cs

@@ -796,6 +796,8 @@ namespace MediaBrowser.Api.Images
         /// <param name="file2">The file2.</param>
         private void SwapFiles(string file1, string file2)
         {
+            Directory.CreateDirectory(_appPaths.TempDirectory);
+
             var temp1 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
             var temp2 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
 

+ 67 - 9
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
@@ -23,7 +24,7 @@ namespace MediaBrowser.Api.LiveTv
         [ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public ChannelType? Type { get; set; }
 
-        [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string UserId { get; set; }
     }
 
@@ -38,7 +39,7 @@ namespace MediaBrowser.Api.LiveTv
         [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Id { get; set; }
 
-        [ApiMember(Name = "UserId", Description = "Optional user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string UserId { get; set; }
     }
 
@@ -48,6 +49,9 @@ namespace MediaBrowser.Api.LiveTv
     {
         [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string ChannelId { get; set; }
+
+        [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string UserId { get; set; }
     }
 
     [Route("/LiveTv/Recordings/{Id}", "GET")]
@@ -56,6 +60,9 @@ namespace MediaBrowser.Api.LiveTv
     {
         [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Id { get; set; }
+
+        [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string UserId { get; set; }
     }
 
     [Route("/LiveTv/Timers/{Id}", "GET")]
@@ -100,14 +107,36 @@ namespace MediaBrowser.Api.LiveTv
         [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Id { get; set; }
     }
-    
+
+    [Route("/LiveTv/Timers/{Id}", "POST")]
+    [Api(Description = "Updates a live tv timer")]
+    public class UpdateTimer : TimerInfoDto, IReturnVoid
+    {
+    }
+
+    [Route("/LiveTv/Timers/{Id}", "GET")]
+    [Api(Description = "Gets a live tv series timer")]
+    public class GetSeriesTimer : IReturn<TimerInfoDto>
+    {
+        [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+    }
+
+    [Route("/LiveTv/SeriesTimers", "GET")]
+    [Api(Description = "Gets live tv series timers")]
+    public class GetSeriesTimers : IReturn<QueryResult<SeriesTimerInfoDto>>
+    {
+    }
+
     public class LiveTvService : BaseApiService
     {
         private readonly ILiveTvManager _liveTvManager;
+        private readonly IUserManager _userManager;
 
-        public LiveTvService(ILiveTvManager liveTvManager)
+        public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager)
         {
             _liveTvManager = liveTvManager;
+            _userManager = userManager;
         }
 
         public object Get(GetServices request)
@@ -134,14 +163,16 @@ namespace MediaBrowser.Api.LiveTv
                 ChannelType = request.Type,
                 UserId = request.UserId
 
-            });
+            }, CancellationToken.None).Result;
 
             return ToOptimizedResult(result);
         }
 
         public object Get(GetChannel request)
         {
-            var result = _liveTvManager.GetChannelInfoDto(request.Id, request.UserId);
+            var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
+
+            var result = _liveTvManager.GetChannel(request.Id, CancellationToken.None, user).Result;
 
             return ToOptimizedResult(result);
         }
@@ -162,7 +193,8 @@ namespace MediaBrowser.Api.LiveTv
         {
             var result = _liveTvManager.GetRecordings(new RecordingQuery
             {
-                ChannelId = request.ChannelId
+                ChannelId = request.ChannelId,
+                UserId = request.UserId
 
             }, CancellationToken.None).Result;
 
@@ -171,7 +203,9 @@ namespace MediaBrowser.Api.LiveTv
 
         public object Get(GetRecording request)
         {
-            var result = _liveTvManager.GetRecording(request.Id, CancellationToken.None).Result;
+            var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
+
+            var result = _liveTvManager.GetRecording(request.Id, CancellationToken.None, user).Result;
 
             return ToOptimizedResult(result);
         }
@@ -207,5 +241,29 @@ namespace MediaBrowser.Api.LiveTv
 
             Task.WaitAll(task);
         }
+
+        public void Post(UpdateTimer request)
+        {
+            var task = _liveTvManager.UpdateTimer(request, CancellationToken.None);
+
+            Task.WaitAll(task);
+        }
+
+        public object Get(GetSeriesTimers request)
+        {
+            var result = _liveTvManager.GetSeriesTimers(new SeriesTimerQuery
+            { 
+
+            }, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Get(GetSeriesTimer request)
+        {
+            var result = _liveTvManager.GetSeriesTimer(request.Id, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
     }
 }

+ 7 - 26
MediaBrowser.Common.Implementations/BaseApplicationPaths.cs

@@ -2,7 +2,6 @@
 using System;
 using System.Configuration;
 using System.IO;
-using System.Reflection;
 
 namespace MediaBrowser.Common.Implementations
 {
@@ -81,10 +80,6 @@ namespace MediaBrowser.Common.Implementations
             }
         }
 
-        /// <summary>
-        /// The _image cache path
-        /// </summary>
-        private string _imageCachePath;
         /// <summary>
         /// Gets the image cache path.
         /// </summary>
@@ -93,14 +88,7 @@ namespace MediaBrowser.Common.Implementations
         {
             get
             {
-                if (_imageCachePath == null)
-                {
-                    _imageCachePath = Path.Combine(CachePath, "images");
-
-                    Directory.CreateDirectory(_imageCachePath);
-                }
-
-                return _imageCachePath;
+                return Path.Combine(CachePath, "images");
             }
         }
 
@@ -233,7 +221,7 @@ namespace MediaBrowser.Common.Implementations
         {
             get
             {
-                if (_cachePath == null)
+                if (string.IsNullOrEmpty(_cachePath))
                 {
                     _cachePath = Path.Combine(ProgramDataPath, "cache");
 
@@ -242,12 +230,12 @@ namespace MediaBrowser.Common.Implementations
 
                 return _cachePath;
             }
+            set
+            {
+                _cachePath = value;
+            }
         }
 
-        /// <summary>
-        /// The _temp directory
-        /// </summary>
-        private string _tempDirectory;
         /// <summary>
         /// Gets the folder path to the temp directory within the cache folder
         /// </summary>
@@ -256,14 +244,7 @@ namespace MediaBrowser.Common.Implementations
         {
             get
             {
-                if (_tempDirectory == null)
-                {
-                    _tempDirectory = Path.Combine(CachePath, "temp");
-
-                    Directory.CreateDirectory(_tempDirectory);
-                }
-
-                return _tempDirectory;
+                return Path.Combine(CachePath, "temp");
             }
         }
 

+ 38 - 1
MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Configuration;
+using System.IO;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Logging;
@@ -84,6 +85,8 @@ namespace MediaBrowser.Common.Implementations.Configuration
             CommonApplicationPaths = applicationPaths;
             XmlSerializer = xmlSerializer;
             Logger = logManager.GetLogger(GetType().Name);
+
+            UpdateCachePath();
         }
 
         /// <summary>
@@ -109,6 +112,8 @@ namespace MediaBrowser.Common.Implementations.Configuration
         /// </summary>
         protected virtual void OnConfigurationUpdated()
         {
+            UpdateCachePath();
+
             EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger);
         }
 
@@ -124,8 +129,40 @@ namespace MediaBrowser.Common.Implementations.Configuration
                 throw new ArgumentNullException("newConfiguration");
             }
 
+            ValidateCachePath(newConfiguration);
+
             CommonConfiguration = newConfiguration;
             SaveConfiguration();
         }
+
+        /// <summary>
+        /// Updates the items by name path.
+        /// </summary>
+        private void UpdateCachePath()
+        {
+            ((BaseApplicationPaths)CommonApplicationPaths).CachePath = string.IsNullOrEmpty(CommonConfiguration.CachePath) ? 
+                null : 
+                CommonConfiguration.CachePath;
+        }
+
+        /// <summary>
+        /// Replaces the cache path.
+        /// </summary>
+        /// <param name="newConfig">The new configuration.</param>
+        /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
+        private void ValidateCachePath(BaseApplicationConfiguration newConfig)
+        {
+            var newPath = newConfig.CachePath;
+
+            if (!string.IsNullOrWhiteSpace(newPath)
+                && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
+            {
+                // Validate
+                if (!Directory.Exists(newPath))
+                {
+                    throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
+                }
+            }
+        }
     }
 }

+ 2 - 0
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -445,6 +445,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
         {
             ValidateParams(options.Url, options.CancellationToken);
 
+            Directory.CreateDirectory(_appPaths.TempDirectory);
+
             var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
 
             if (options.Progress == null)

+ 4 - 4
MediaBrowser.Controller/IServerApplicationPaths.cs

@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller
         /// Gets the path to the Images By Name directory
         /// </summary>
         /// <value>The images by name path.</value>
-        string ItemsByNamePath { get; set; }
+        string ItemsByNamePath { get; }
 
         /// <summary>
         /// Gets the path to the People directory
@@ -51,13 +51,13 @@ namespace MediaBrowser.Controller
         /// </summary>
         /// <value>The game genre path.</value>
         string GameGenrePath { get; }
-        
+
         /// <summary>
         /// Gets the artists path.
         /// </summary>
         /// <value>The artists path.</value>
         string ArtistsPath { get; }
-        
+
         /// <summary>
         /// Gets the path to the Studio directory
         /// </summary>
@@ -87,7 +87,7 @@ namespace MediaBrowser.Controller
         /// </summary>
         /// <value>The media info images path.</value>
         string MediaInfoImagesPath { get; }
-        
+
         /// <summary>
         /// Gets the path to the user configuration directory
         /// </summary>

+ 46 - 10
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
 using System.Threading;
@@ -54,17 +55,28 @@ namespace MediaBrowser.Controller.LiveTv
         /// Gets the channels.
         /// </summary>
         /// <param name="query">The query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>IEnumerable{Channel}.</returns>
-        QueryResult<ChannelInfoDto> GetChannels(ChannelQuery query);
+        Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the recording.
         /// </summary>
         /// <param name="id">The identifier.</param>
+        /// <param name="user">The user.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{RecordingInfoDto}.</returns>
-        Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken);
+        Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken, User user = null);
 
+        /// <summary>
+        /// Gets the channel.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>Task{RecordingInfoDto}.</returns>
+        Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null);
+        
         /// <summary>
         /// Gets the timer.
         /// </summary>
@@ -73,6 +85,14 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task{TimerInfoDto}.</returns>
         Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Gets the series timer.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{TimerInfoDto}.</returns>
+        Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken);
+        
         /// <summary>
         /// Gets the recordings.
         /// </summary>
@@ -90,19 +110,19 @@ namespace MediaBrowser.Controller.LiveTv
         Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken);
 
         /// <summary>
-        /// Gets the channel.
+        /// Gets the series timers.
         /// </summary>
-        /// <param name="id">The identifier.</param>
-        /// <returns>Channel.</returns>
-        Channel GetChannel(string id);
-
+        /// <param name="query">The query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{QueryResult{SeriesTimerInfoDto}}.</returns>
+        Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken);
+        
         /// <summary>
         /// Gets the channel.
         /// </summary>
         /// <param name="id">The identifier.</param>
-        /// <param name="userId">The user identifier.</param>
         /// <returns>Channel.</returns>
-        ChannelInfoDto GetChannelInfoDto(string id, string userId);
+        Channel GetChannel(string id);
 
         /// <summary>
         /// Gets the programs.
@@ -111,5 +131,21 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>IEnumerable{ProgramInfo}.</returns>
         Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Updates the timer.
+        /// </summary>
+        /// <param name="timer">The timer.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Updates the timer.
+        /// </summary>
+        /// <param name="timer">The timer.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
     }
 }

+ 9 - 2
MediaBrowser.Controller/LiveTv/ILiveTvService.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Net;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -55,6 +54,14 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task.</returns>
         Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Updates the timer asynchronous.
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken);
+
         /// <summary>
         /// Updates the series timer asynchronous.
         /// </summary>

+ 0 - 6
MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs

@@ -15,11 +15,5 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The type of the MIME.</value>
         public string MimeType { get; set; }
-
-        /// <summary>
-        /// Gets or sets the image path.
-        /// </summary>
-        /// <value>The image path.</value>
-        public string ImagePath { get; set; }
     }
 }

+ 27 - 15
MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// ChannelId of the recording.
         /// </summary>
         public string ChannelId { get; set; }
-
+        
         /// <summary>
         /// ChannelName of the recording.
         /// </summary>
@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The program identifier.</value>
         public string ProgramId { get; set; }
-
+        
         /// <summary>
         /// Name of the recording.
         /// </summary>
@@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// <summary>
         /// Description of the recording.
         /// </summary>
-        public string Description { get; set; }
+        public string Overview { get; set; }
 
         /// <summary>
         /// The start date of the recording, in UTC.
@@ -47,18 +47,6 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         public DateTime EndDate { get; set; }
 
-        /// <summary>
-        /// Gets or sets the pre padding seconds.
-        /// </summary>
-        /// <value>The pre padding seconds.</value>
-        public int PrePaddingSeconds { get; set; }
-
-        /// <summary>
-        /// Gets or sets the post padding seconds.
-        /// </summary>
-        /// <value>The post padding seconds.</value>
-        public int PostPaddingSeconds { get; set; }
-
         /// <summary>
         /// Gets or sets the type of the recurrence.
         /// </summary>
@@ -77,6 +65,30 @@ namespace MediaBrowser.Controller.LiveTv
         /// <value>The priority.</value>
         public int Priority { get; set; }
 
+        /// <summary>
+        /// Gets or sets the requested pre padding seconds.
+        /// </summary>
+        /// <value>The requested pre padding seconds.</value>
+        public int RequestedPrePaddingSeconds { get; set; }
+
+        /// <summary>
+        /// Gets or sets the requested post padding seconds.
+        /// </summary>
+        /// <value>The requested post padding seconds.</value>
+        public int RequestedPostPaddingSeconds { get; set; }
+
+        /// <summary>
+        /// Gets or sets the required pre padding seconds.
+        /// </summary>
+        /// <value>The required pre padding seconds.</value>
+        public int RequiredPrePaddingSeconds { get; set; }
+
+        /// <summary>
+        /// Gets or sets the required post padding seconds.
+        /// </summary>
+        /// <value>The required post padding seconds.</value>
+        public int RequiredPostPaddingSeconds { get; set; }
+        
         public SeriesTimerInfo()
         {
             Days = new List<DayOfWeek>();

+ 22 - 86
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -759,11 +759,30 @@ namespace MediaBrowser.Controller.Providers
                         break;
                     }
 
-                case "MediaInfo":
+                case "Format3D":
                     {
-                        using (var subtree = reader.ReadSubtree())
+                        var video = item as Video;
+
+                        if (video != null)
                         {
-                            FetchFromMediaInfoNode(subtree, item);
+                            var val = reader.ReadElementContentAsString();
+
+                            if (string.Equals("HSBS", val))
+                            {
+                                video.Video3DFormat = Video3DFormat.HalfSideBySide;
+                            }
+                            else if (string.Equals("HTAB", val))
+                            {
+                                video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
+                            }
+                            else if (string.Equals("FTAB", val))
+                            {
+                                video.Video3DFormat = Video3DFormat.FullTopAndBottom;
+                            }
+                            else if (string.Equals("FSBS", val))
+                            {
+                                video.Video3DFormat = Video3DFormat.FullSideBySide;
+                            }
                         }
                         break;
                     }
@@ -774,89 +793,6 @@ namespace MediaBrowser.Controller.Providers
             }
         }
 
-        /// <summary>
-        /// Fetches from media info node.
-        /// </summary>
-        /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        private void FetchFromMediaInfoNode(XmlReader reader, T item)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "Video":
-                            {
-                                using (var subtree = reader.ReadSubtree())
-                                {
-                                    FetchFromMediaInfoVideoNode(subtree, item);
-                                }
-                                break;
-                            }
-
-                        default:
-                            reader.Skip();
-                            break;
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Fetches from media info video node.
-        /// </summary>
-        /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        private void FetchFromMediaInfoVideoNode(XmlReader reader, T item)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "Format3D":
-                            {
-                                var video = item as Video;
-
-                                if (video != null)
-                                {
-                                    var val = reader.ReadElementContentAsString();
-
-                                    if (string.Equals("HSBS", val))
-                                    {
-                                        video.Video3DFormat = Video3DFormat.HalfSideBySide;
-                                    }
-                                    else if (string.Equals("HTAB", val))
-                                    {
-                                        video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
-                                    }
-                                    else if (string.Equals("FTAB", val))
-                                    {
-                                        video.Video3DFormat = Video3DFormat.FullTopAndBottom;
-                                    }
-                                    else if (string.Equals("FSBS", val))
-                                    {
-                                        video.Video3DFormat = Video3DFormat.FullSideBySide;
-                                    }
-                                }
-                                break;
-                            }
-
-                        default:
-                            reader.Skip();
-                            break;
-                    }
-                }
-            }
-        }
-
         /// <summary>
         /// Fetches from taglines node.
         /// </summary>

+ 3 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -248,6 +248,9 @@
     <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingStatus.cs">
       <Link>LiveTv\RecordingStatus.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\LiveTv\SeriesTimerInfoDto.cs">
+      <Link>LiveTv\SeriesTimerInfoDto.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\LiveTv\TimerInfoDto.cs">
       <Link>LiveTv\TimerInfoDto.cs</Link>
     </Compile>

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -235,6 +235,9 @@
     <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingStatus.cs">
       <Link>LiveTv\RecordingStatus.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\LiveTv\SeriesTimerInfoDto.cs">
+      <Link>LiveTv\SeriesTimerInfoDto.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\LiveTv\TimerInfoDto.cs">
       <Link>LiveTv\TimerInfoDto.cs</Link>
     </Compile>

+ 7 - 1
MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs

@@ -43,7 +43,13 @@ namespace MediaBrowser.Model.Configuration
         /// </summary>
         /// <value><c>true</c> if this instance is first run; otherwise, <c>false</c>.</value>
         public bool IsStartupWizardCompleted { get; set; }
-        
+
+        /// <summary>
+        /// Gets or sets the cache path.
+        /// </summary>
+        /// <value>The cache path.</value>
+        public string CachePath { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
         /// </summary>

+ 7 - 0
MediaBrowser.Model/LiveTv/ProgramInfoDto.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Model.LiveTv
 {
@@ -101,6 +102,12 @@ namespace MediaBrowser.Model.LiveTv
         /// <value>The episode title.</value>
         public string EpisodeTitle { get; set; }
 
+        /// <summary>
+        /// Gets or sets the user data.
+        /// </summary>
+        /// <value>The user data.</value>
+        public UserItemDataDto UserData { get; set; }
+
         public ProgramInfoDto()
         {
             Genres = new List<string>();

+ 7 - 0
MediaBrowser.Model/LiveTv/RecordingInfoDto.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Model.LiveTv
 {
@@ -123,6 +124,12 @@ namespace MediaBrowser.Model.LiveTv
         /// <value>The audio.</value>
         public ProgramAudio? Audio { get; set; }
 
+        /// <summary>
+        /// Gets or sets the user data.
+        /// </summary>
+        /// <value>The user data.</value>
+        public UserItemDataDto UserData { get; set; }
+
         public RecordingInfoDto()
         {
             Genres = new List<string>();

+ 10 - 0
MediaBrowser.Model/LiveTv/RecordingQuery.cs

@@ -10,6 +10,12 @@
         /// </summary>
         /// <value>The channel identifier.</value>
         public string ChannelId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the user identifier.
+        /// </summary>
+        /// <value>The user identifier.</value>
+        public string UserId { get; set; }
     }
 
     public class TimerQuery
@@ -20,4 +26,8 @@
         /// <value>The channel identifier.</value>
         public string ChannelId { get; set; }
     }
+
+    public class SeriesTimerQuery
+    {
+    }
 }

+ 8 - 1
MediaBrowser.Model/LiveTv/RecordingStatus.cs

@@ -7,7 +7,7 @@ namespace MediaBrowser.Model.LiveTv
         Scheduled,
         InProgress,
         Completed,
-        Abored,
+        Aborted,
         Cancelled,
         ConflictedOk,
         ConflictedNotOk,
@@ -22,4 +22,11 @@ namespace MediaBrowser.Model.LiveTv
         NewProgramEventsAllChannels,
         AllProgramEventsAllChannels
     }
+
+    public enum DayPattern
+    {
+        Daily,
+        Weekdays,
+        Weekends
+    }
 }

+ 120 - 0
MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs

@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.LiveTv
+{
+    public class SeriesTimerInfoDto
+    {
+        /// <summary>
+        /// Id of the recording.
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets the external identifier.
+        /// </summary>
+        /// <value>The external identifier.</value>
+        public string ExternalId { get; set; }
+        
+        /// <summary>
+        /// ChannelId of the recording.
+        /// </summary>
+        public string ChannelId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the external channel identifier.
+        /// </summary>
+        /// <value>The external channel identifier.</value>
+        public string ExternalChannelId { get; set; }
+        
+        /// <summary>
+        /// ChannelName of the recording.
+        /// </summary>
+        public string ChannelName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the program identifier.
+        /// </summary>
+        /// <value>The program identifier.</value>
+        public string ProgramId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the external program identifier.
+        /// </summary>
+        /// <value>The external program identifier.</value>
+        public string ExternalProgramId { get; set; }
+        
+        /// <summary>
+        /// Name of the recording.
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Description of the recording.
+        /// </summary>
+        public string Overview { get; set; }
+
+        /// <summary>
+        /// The start date of the recording, in UTC.
+        /// </summary>
+        public DateTime StartDate { get; set; }
+
+        /// <summary>
+        /// The end date of the recording, in UTC.
+        /// </summary>
+        public DateTime EndDate { get; set; }
+
+        /// <summary>
+        /// Gets or sets the type of the recurrence.
+        /// </summary>
+        /// <value>The type of the recurrence.</value>
+        public RecurrenceType RecurrenceType { get; set; }
+
+        /// <summary>
+        /// Gets or sets the days.
+        /// </summary>
+        /// <value>The days.</value>
+        public List<DayOfWeek> Days { get; set; }
+
+        /// <summary>
+        /// Gets or sets the day pattern.
+        /// </summary>
+        /// <value>The day pattern.</value>
+        public DayPattern? DayPattern { get; set; }
+
+        /// <summary>
+        /// Gets or sets the priority.
+        /// </summary>
+        /// <value>The priority.</value>
+        public int Priority { get; set; }
+
+        /// <summary>
+        /// Gets or sets the requested pre padding seconds.
+        /// </summary>
+        /// <value>The requested pre padding seconds.</value>
+        public int RequestedPrePaddingSeconds { get; set; }
+
+        /// <summary>
+        /// Gets or sets the requested post padding seconds.
+        /// </summary>
+        /// <value>The requested post padding seconds.</value>
+        public int RequestedPostPaddingSeconds { get; set; }
+
+        /// <summary>
+        /// Gets or sets the required pre padding seconds.
+        /// </summary>
+        /// <value>The required pre padding seconds.</value>
+        public int RequiredPrePaddingSeconds { get; set; }
+
+        /// <summary>
+        /// Gets or sets the required post padding seconds.
+        /// </summary>
+        /// <value>The required post padding seconds.</value>
+        public int RequiredPostPaddingSeconds { get; set; }
+
+        public SeriesTimerInfoDto()
+        {
+            Days = new List<DayOfWeek>();
+        }
+    }
+}

+ 12 - 0
MediaBrowser.Model/LiveTv/TimerInfoDto.cs

@@ -20,6 +20,12 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         public string ChannelId { get; set; }
 
+        /// <summary>
+        /// Gets or sets the external channel identifier.
+        /// </summary>
+        /// <value>The external channel identifier.</value>
+        public string ExternalChannelId { get; set; }
+        
         /// <summary>
         /// ChannelName of the recording.
         /// </summary>
@@ -63,6 +69,12 @@ namespace MediaBrowser.Model.LiveTv
         /// <value>The series timer identifier.</value>
         public string SeriesTimerId { get; set; }
 
+        /// <summary>
+        /// Gets or sets the external series timer identifier.
+        /// </summary>
+        /// <value>The external series timer identifier.</value>
+        public string ExternalSeriesTimerId { get; set; }
+        
         /// <summary>
         /// Gets or sets the requested pre padding seconds.
         /// </summary>

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -77,6 +77,7 @@
     <Compile Include="LiveTv\ProgramQuery.cs" />
     <Compile Include="LiveTv\RecordingQuery.cs" />
     <Compile Include="LiveTv\RecordingStatus.cs" />
+    <Compile Include="LiveTv\SeriesTimerInfoDto.cs" />
     <Compile Include="LiveTv\TimerInfoDto.cs" />
     <Compile Include="Providers\ImageProviderInfo.cs" />
     <Compile Include="Providers\RemoteImageInfo.cs" />

+ 6 - 0
MediaBrowser.Model/System/SystemInfo.cs

@@ -98,6 +98,12 @@ namespace MediaBrowser.Model.System
         /// <value>The items by name path.</value>
         public string ItemsByNamePath { get; set; }
 
+        /// <summary>
+        /// Gets or sets the cache path.
+        /// </summary>
+        /// <value>The cache path.</value>
+        public string CachePath { get; set; }
+        
         /// <summary>
         /// Gets or sets the log path.
         /// </summary>

+ 2 - 1
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -263,7 +263,8 @@ namespace MediaBrowser.Providers.Movies
                 id = item.GetProviderId(MetadataProviders.Imdb);
             }
 
-            if (string.IsNullOrEmpty(id))
+            // Don't search for music video id's because it is very easy to misidentify. 
+            if (string.IsNullOrEmpty(id) && !(item is MusicVideo))
             {
                 id = await FindId(item, cancellationToken).ConfigureAwait(false);
             }

+ 1 - 9
MediaBrowser.Providers/Savers/XmlSaverHelpers.cs

@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.Savers
                     "LocalTitle",
                     "LockData",
                     "LockedFields",
-                    "MediaInfo",
+                    "Format3D",
                     "MPAARating",
                     "MusicbrainzId",
                     "MusicBrainzReleaseGroupId",
@@ -536,10 +536,6 @@ namespace MediaBrowser.Providers.Savers
 
             if (video != null && video.Video3DFormat.HasValue)
             {
-                builder.Append("<MediaInfo>");
-
-                builder.Append("<Video>");
-
                 switch (video.Video3DFormat.Value)
                 {
                     case Video3DFormat.FullSideBySide:
@@ -555,10 +551,6 @@ namespace MediaBrowser.Providers.Savers
                         builder.Append("<Format3D>HTAB</Format3D>");
                         break;
                 }
-
-                builder.Append("</Video>");
-
-                builder.Append("</MediaInfo>");
             }
         }
     }

+ 20 - 11
MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs

@@ -69,10 +69,9 @@ namespace MediaBrowser.Server.Implementations.Configuration
         /// </summary>
         private void UpdateItemsByNamePath()
         {
-            if (!string.IsNullOrEmpty(Configuration.ItemsByNamePath))
-            {
-                ApplicationPaths.ItemsByNamePath = Configuration.ItemsByNamePath;
-            }
+            ((ServerApplicationPaths) ApplicationPaths).ItemsByNamePath = string.IsNullOrEmpty(Configuration.ItemsByNamePath) ? 
+                null : 
+                Configuration.ItemsByNamePath;
         }
 
         /// <summary>
@@ -84,19 +83,29 @@ namespace MediaBrowser.Server.Implementations.Configuration
         {
             var newConfig = (ServerConfiguration) newConfiguration;
 
-            var newIbnPath = newConfig.ItemsByNamePath;
+            ValidateItemByNamePath(newConfig);
+
+            base.ReplaceConfiguration(newConfiguration);
+        }
+
+        /// <summary>
+        /// Replaces the item by name path.
+        /// </summary>
+        /// <param name="newConfig">The new configuration.</param>
+        /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
+        private void ValidateItemByNamePath(ServerConfiguration newConfig)
+        {
+            var newPath = newConfig.ItemsByNamePath;
 
-            if (!string.IsNullOrWhiteSpace(newIbnPath)
-                && !string.Equals(Configuration.ItemsByNamePath ?? string.Empty, newIbnPath))
+            if (!string.IsNullOrWhiteSpace(newPath)
+                && !string.Equals(Configuration.ItemsByNamePath ?? string.Empty, newPath))
             {
                 // Validate
-                if (!Directory.Exists(newIbnPath))
+                if (!Directory.Exists(newPath))
                 {
-                    throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newConfig.ItemsByNamePath));
+                    throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
                 }
             }
-
-            base.ReplaceConfiguration(newConfiguration);
         }
     }
 }

+ 27 - 11
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -53,10 +53,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IServerApplicationPaths _appPaths;
 
-        private readonly string _croppedWhitespaceImageCachePath;
-        private readonly string _enhancedImageCachePath;
-        private readonly string _resizedImageCachePath;
-
         public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
         {
             _logger = logger;
@@ -64,10 +60,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
             _jsonSerializer = jsonSerializer;
             _appPaths = appPaths;
 
-            _croppedWhitespaceImageCachePath = Path.Combine(appPaths.ImageCachePath, "cropped-images");
-            _enhancedImageCachePath = Path.Combine(appPaths.ImageCachePath, "enhanced-images");
-            _resizedImageCachePath = Path.Combine(appPaths.ImageCachePath, "resized-images");
-
             _saveImageSizeTimer = new Timer(SaveImageSizeCallback, null, Timeout.Infinite, Timeout.Infinite);
 
             Dictionary<Guid, ImageSize> sizeDictionary;
@@ -92,6 +84,30 @@ namespace MediaBrowser.Server.Implementations.Drawing
             _cachedImagedSizes = new ConcurrentDictionary<Guid, ImageSize>(sizeDictionary);
         }
 
+        private string ResizedImageCachePath
+        {
+            get
+            {
+                return Path.Combine(_appPaths.ImageCachePath, "resized-images");
+            }
+        }
+
+        private string EnhancedImageCachePath
+        {
+            get
+            {
+                return Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
+            }
+        }
+
+        private string CroppedWhitespaceImageCachePath
+        {
+            get
+            {
+                return Path.Combine(_appPaths.ImageCachePath, "cropped-images");
+            }
+        }
+
         public void AddParts(IEnumerable<IImageEnhancer> enhancers)
         {
             ImageEnhancers = enhancers.ToArray();
@@ -391,7 +407,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
             var name = originalImagePath;
             name += "datemodified=" + dateModified.Ticks;
 
-            var croppedImagePath = GetCachePath(_croppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
+            var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
 
             var semaphore = GetLock(croppedImagePath);
 
@@ -480,7 +496,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
                 filename += "b=" + backgroundColor;
             }
 
-            return GetCachePath(_resizedImageCachePath, filename, Path.GetExtension(originalPath));
+            return GetCachePath(ResizedImageCachePath, filename, Path.GetExtension(originalPath));
         }
 
         /// <summary>
@@ -708,7 +724,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
             var cacheGuid = GetImageCacheTag(item, imageType, originalImagePath, dateModified, supportedEnhancers);
 
             // All enhanced images are saved as png to allow transparency
-            var enhancedImagePath = GetCachePath(_enhancedImageCachePath, cacheGuid + ".png");
+            var enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + ".png");
 
             var semaphore = GetLock(enhancedImagePath);
 

+ 6 - 2
MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
@@ -7,6 +8,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using System;
+using System.IO;
 using System.Linq;
 using System.Net;
 using System.Threading;
@@ -18,12 +20,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
     {
         private readonly ILiveTvManager _liveTvManager;
         private readonly IProviderManager _providerManager;
+        private readonly IFileSystem _fileSystem;
 
-        public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager)
+        public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
         {
             _liveTvManager = liveTvManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
         }
 
         public override bool Supports(BaseItem item)

+ 324 - 0
MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs

@@ -0,0 +1,324 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Logging;
+using System;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+    public class LiveTvDtoService
+    {
+        private readonly ILogger _logger;
+        private readonly IImageProcessor _imageProcessor;
+
+        private readonly IUserDataManager _userDataManager;
+        private readonly IDtoService _dtoService;
+
+        public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger)
+        {
+            _dtoService = dtoService;
+            _userDataManager = userDataManager;
+            _imageProcessor = imageProcessor;
+            _logger = logger;
+        }
+
+        public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service)
+        {
+            var dto = new TimerInfoDto
+            {
+                Id = GetInternalTimerId(service.Name, info.Id).ToString("N"),
+                ChannelName = info.ChannelName,
+                Overview = info.Overview,
+                EndDate = info.EndDate,
+                Name = info.Name,
+                StartDate = info.StartDate,
+                ExternalId = info.Id,
+                ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
+                Status = info.Status,
+                SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N"),
+                RequestedPostPaddingSeconds = info.RequestedPostPaddingSeconds,
+                RequestedPrePaddingSeconds = info.RequestedPrePaddingSeconds,
+                RequiredPostPaddingSeconds = info.RequiredPostPaddingSeconds,
+                RequiredPrePaddingSeconds = info.RequiredPrePaddingSeconds,
+                ExternalChannelId = info.ChannelId,
+                ExternalSeriesTimerId = info.SeriesTimerId
+            };
+
+            var duration = info.EndDate - info.StartDate;
+            dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
+
+            if (!string.IsNullOrEmpty(info.ProgramId))
+            {
+                dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
+            }
+
+            return dto;
+        }
+
+        public SeriesTimerInfoDto GetSeriesTimerInfoDto(SeriesTimerInfo info, ILiveTvService service)
+        {
+            var dto = new SeriesTimerInfoDto
+            {
+                Id = GetInternalSeriesTimerId(service.Name, info.Id).ToString("N"),
+                ChannelName = info.ChannelName,
+                Overview = info.Overview,
+                EndDate = info.EndDate,
+                Name = info.Name,
+                StartDate = info.StartDate,
+                ExternalId = info.Id,
+                ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
+                RequestedPostPaddingSeconds = info.RequestedPostPaddingSeconds,
+                RequestedPrePaddingSeconds = info.RequestedPrePaddingSeconds,
+                RequiredPostPaddingSeconds = info.RequiredPostPaddingSeconds,
+                RequiredPrePaddingSeconds = info.RequiredPrePaddingSeconds,
+                Days = info.Days,
+                Priority = info.Priority,
+                RecurrenceType = info.RecurrenceType,
+                ExternalChannelId = info.ChannelId,
+                ExternalProgramId = info.ProgramId
+            };
+
+            if (!string.IsNullOrEmpty(info.ProgramId))
+            {
+                dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
+            }
+
+            DayPattern? pattern = null;
+
+            if (info.Days != null && info.Days.Count > 0)
+            {
+                if (info.Days.Count == 7)
+                {
+                    pattern = DayPattern.Daily;
+                }
+                else if (info.Days.Count == 2)
+                {
+                    if (info.Days.Contains(DayOfWeek.Saturday) && info.Days.Contains(DayOfWeek.Sunday))
+                    {
+                        pattern = DayPattern.Weekends;
+                    }
+                }
+                else if (info.Days.Count == 5)
+                {
+                    if (info.Days.Contains(DayOfWeek.Monday) && info.Days.Contains(DayOfWeek.Tuesday) && info.Days.Contains(DayOfWeek.Wednesday) && info.Days.Contains(DayOfWeek.Thursday) && info.Days.Contains(DayOfWeek.Friday))
+                    {
+                        pattern = DayPattern.Weekdays;
+                    }
+                }
+            }
+
+            dto.DayPattern = pattern;
+
+            return dto;
+        }
+
+        public RecordingInfoDto GetRecordingInfoDto(RecordingInfo info, ILiveTvService service, User user = null)
+        {
+            var dto = new RecordingInfoDto
+            {
+                Id = GetInternalRecordingId(service.Name, info.Id).ToString("N"),
+                ChannelName = info.ChannelName,
+                Overview = info.Overview,
+                EndDate = info.EndDate,
+                Name = info.Name,
+                StartDate = info.StartDate,
+                ExternalId = info.Id,
+                ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
+                Status = info.Status,
+                Path = info.Path,
+                Genres = info.Genres,
+                IsRepeat = info.IsRepeat,
+                EpisodeTitle = info.EpisodeTitle,
+                ChannelType = info.ChannelType,
+                MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video,
+                CommunityRating = info.CommunityRating,
+                OfficialRating = info.OfficialRating,
+                Audio = info.Audio,
+                IsHD = info.IsHD
+            };
+
+            if (user != null)
+            {
+                //dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
+            }
+
+            var duration = info.EndDate - info.StartDate;
+            dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
+
+            if (!string.IsNullOrEmpty(info.ProgramId))
+            {
+                dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
+            }
+
+            return dto;
+        }
+
+        /// <summary>
+        /// Gets the channel info dto.
+        /// </summary>
+        /// <param name="info">The info.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>ChannelInfoDto.</returns>
+        public ChannelInfoDto GetChannelInfoDto(Channel info, User user = null)
+        {
+            var dto = new ChannelInfoDto
+            {
+                Name = info.Name,
+                ServiceName = info.ServiceName,
+                ChannelType = info.ChannelType,
+                Number = info.ChannelNumber,
+                Type = info.GetType().Name,
+                Id = info.Id.ToString("N"),
+                MediaType = info.MediaType
+            };
+
+            if (user != null)
+            {
+                dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
+            }
+
+            var imageTag = GetLogoImageTag(info);
+
+            if (imageTag.HasValue)
+            {
+                dto.ImageTags[ImageType.Primary] = imageTag.Value;
+            }
+
+            return dto;
+        }
+
+        public ProgramInfoDto GetProgramInfoDto(ProgramInfo program, Channel channel, User user = null)
+        {
+            var dto = new ProgramInfoDto
+            {
+                Id = GetInternalProgramId(channel.ServiceName, program.Id).ToString("N"),
+                ChannelId = channel.Id.ToString("N"),
+                Overview = program.Overview,
+                EndDate = program.EndDate,
+                Genres = program.Genres,
+                ExternalId = program.Id,
+                Name = program.Name,
+                ServiceName = channel.ServiceName,
+                StartDate = program.StartDate,
+                OfficialRating = program.OfficialRating,
+                IsHD = program.IsHD,
+                OriginalAirDate = program.OriginalAirDate,
+                Audio = program.Audio,
+                CommunityRating = program.CommunityRating,
+                AspectRatio = program.AspectRatio,
+                IsRepeat = program.IsRepeat,
+                EpisodeTitle = program.EpisodeTitle
+            };
+
+            if (user != null)
+            {
+                //dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
+            }
+
+            return dto;
+        }
+
+        private Guid? GetLogoImageTag(Channel info)
+        {
+            var path = info.PrimaryImagePath;
+
+            if (string.IsNullOrEmpty(path))
+            {
+                return null;
+            }
+
+            try
+            {
+                return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error getting channel image info for {0}", ex, info.Name);
+            }
+
+            return null;
+        }
+
+        public Guid GetInternalChannelId(string serviceName, string externalId, string channelName)
+        {
+            var name = serviceName + externalId + channelName;
+
+            return name.ToLower().GetMBId(typeof(Channel));
+        }
+
+        public Guid GetInternalTimerId(string serviceName, string externalId)
+        {
+            var name = serviceName + externalId;
+
+            return name.ToLower().GetMD5();
+        }
+
+        public Guid GetInternalSeriesTimerId(string serviceName, string externalId)
+        {
+            var name = serviceName + externalId;
+
+            return name.ToLower().GetMD5();
+        }
+
+        public Guid GetInternalProgramId(string serviceName, string externalId)
+        {
+            var name = serviceName + externalId;
+
+            return name.ToLower().GetMD5();
+        }
+
+        public Guid GetInternalRecordingId(string serviceName, string externalId)
+        {
+            var name = serviceName + externalId;
+
+            return name.ToLower().GetMD5();
+        }
+
+        public TimerInfo GetTimerInfo(TimerInfoDto dto)
+        {
+            return new TimerInfo
+            {
+                Id = dto.ExternalId,
+                ChannelName = dto.ChannelName,
+                Overview = dto.Overview,
+                EndDate = dto.EndDate,
+                Name = dto.Name,
+                StartDate = dto.StartDate,
+                ChannelId = dto.ExternalChannelId,
+                Status = dto.Status,
+                SeriesTimerId = dto.ExternalSeriesTimerId,
+                RequestedPostPaddingSeconds = dto.RequestedPostPaddingSeconds,
+                RequestedPrePaddingSeconds = dto.RequestedPrePaddingSeconds,
+                RequiredPostPaddingSeconds = dto.RequiredPostPaddingSeconds,
+                RequiredPrePaddingSeconds = dto.RequiredPrePaddingSeconds
+            };
+        }
+
+        public SeriesTimerInfo GetSeriesTimerInfo(SeriesTimerInfoDto dto)
+        {
+            return new SeriesTimerInfo
+            {
+                Id = dto.ExternalId,
+                ChannelName = dto.ChannelName,
+                Overview = dto.Overview,
+                EndDate = dto.EndDate,
+                Name = dto.Name,
+                StartDate = dto.StartDate,
+                ChannelId = dto.ExternalChannelId,
+                RequestedPostPaddingSeconds = dto.RequestedPostPaddingSeconds,
+                RequestedPrePaddingSeconds = dto.RequestedPrePaddingSeconds,
+                RequiredPostPaddingSeconds = dto.RequiredPostPaddingSeconds,
+                RequiredPrePaddingSeconds = dto.RequiredPrePaddingSeconds,
+                Days = dto.Days,
+                Priority = dto.Priority,
+                RecurrenceType = dto.RecurrenceType,
+                ProgramId = dto.ExternalProgramId
+            };
+        }
+    }
+}

+ 84 - 212
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -8,7 +8,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
@@ -30,29 +29,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         private readonly IFileSystem _fileSystem;
         private readonly ILogger _logger;
         private readonly IItemRepository _itemRepo;
-        private readonly IImageProcessor _imageProcessor;
-
         private readonly IUserManager _userManager;
+
         private readonly ILocalizationManager _localization;
-        private readonly IUserDataManager _userDataManager;
-        private readonly IDtoService _dtoService;
+        private readonly LiveTvDtoService _tvDtoService;
 
         private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
 
         private List<Channel> _channels = new List<Channel>();
         private List<ProgramInfoDto> _programs = new List<ProgramInfoDto>();
 
-        public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserManager userManager, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService)
+        public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager)
         {
             _appPaths = appPaths;
             _fileSystem = fileSystem;
             _logger = logger;
             _itemRepo = itemRepo;
-            _imageProcessor = imageProcessor;
-            _userManager = userManager;
             _localization = localization;
-            _userDataManager = userDataManager;
-            _dtoService = dtoService;
+            _userManager = userManager;
+
+            _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger);
         }
 
         /// <summary>
@@ -77,62 +73,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             ActiveService = _services.FirstOrDefault();
         }
 
-        /// <summary>
-        /// Gets the channel info dto.
-        /// </summary>
-        /// <param name="info">The info.</param>
-        /// <param name="user">The user.</param>
-        /// <returns>ChannelInfoDto.</returns>
-        public ChannelInfoDto GetChannelInfoDto(Channel info, User user)
-        {
-            var dto = new ChannelInfoDto
-            {
-                Name = info.Name,
-                ServiceName = info.ServiceName,
-                ChannelType = info.ChannelType,
-                Number = info.ChannelNumber,
-                Type = info.GetType().Name,
-                Id = info.Id.ToString("N"),
-                MediaType = info.MediaType
-            };
-
-            if (user != null)
-            {
-                dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
-            }
-
-            var imageTag = GetLogoImageTag(info);
-
-            if (imageTag.HasValue)
-            {
-                dto.ImageTags[ImageType.Primary] = imageTag.Value;
-            }
-
-            return dto;
-        }
-
-        private Guid? GetLogoImageTag(Channel info)
-        {
-            var path = info.PrimaryImagePath;
-
-            if (string.IsNullOrEmpty(path))
-            {
-                return null;
-            }
-
-            try
-            {
-                return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path);
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error getting channel image info for {0}", ex, info.Name);
-            }
-
-            return null;
-        }
-
-        public QueryResult<ChannelInfoDto> GetChannels(ChannelQuery query)
+        public Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken)
         {
             var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
 
@@ -167,14 +108,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 return number;
 
             }).ThenBy(i => i.Name)
-            .Select(i => GetChannelInfoDto(i, user))
+            .Select(i => _tvDtoService.GetChannelInfoDto(i, user))
             .ToArray();
 
-            return new QueryResult<ChannelInfoDto>
+            var result = new QueryResult<ChannelInfoDto>
             {
                 Items = returnChannels,
                 TotalRecordCount = returnChannels.Length
             };
+
+            return Task.FromResult(result);
         }
 
         public Channel GetChannel(string id)
@@ -184,55 +127,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return _channels.FirstOrDefault(i => i.Id == guid);
         }
 
-        public ChannelInfoDto GetChannelInfoDto(string id, string userId)
-        {
-            var channel = GetChannel(id);
-
-            var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(new Guid(userId));
-
-            return channel == null ? null : GetChannelInfoDto(channel, user);
-        }
-
-        private ProgramInfoDto GetProgramInfoDto(ProgramInfo program, Channel channel)
-        {
-            var id = GetInternalProgramIdId(channel.ServiceName, program.Id).ToString("N");
-
-            return new ProgramInfoDto
-            {
-                ChannelId = channel.Id.ToString("N"),
-                Overview = program.Overview,
-                EndDate = program.EndDate,
-                Genres = program.Genres,
-                ExternalId = program.Id,
-                Id = id,
-                Name = program.Name,
-                ServiceName = channel.ServiceName,
-                StartDate = program.StartDate,
-                OfficialRating = program.OfficialRating,
-                IsHD = program.IsHD,
-                OriginalAirDate = program.OriginalAirDate,
-                Audio = program.Audio,
-                CommunityRating = program.CommunityRating,
-                AspectRatio = program.AspectRatio,
-                IsRepeat = program.IsRepeat,
-                EpisodeTitle = program.EpisodeTitle
-            };
-        }
-
-        private Guid GetInternalChannelId(string serviceName, string externalChannelId, string channelName)
-        {
-            var name = serviceName + externalChannelId + channelName;
-
-            return name.ToLower().GetMBId(typeof(Channel));
-        }
-
-        private Guid GetInternalProgramIdId(string serviceName, string externalProgramId)
-        {
-            var name = serviceName + externalProgramId;
-
-            return name.ToLower().GetMD5();
-        }
-
         private async Task<Channel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
         {
             var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name));
@@ -254,7 +148,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 isNew = true;
             }
 
-            var id = GetInternalChannelId(serviceName, channelInfo.Id, channelInfo.Name);
+            var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id, channelInfo.Name);
 
             var item = _itemRepo.RetrieveItem(id) as Channel;
 
@@ -335,7 +229,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
                     var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false);
 
-                    programs.AddRange(channelPrograms.Select(program => GetProgramInfoDto(program, item)));
+                    programs.AddRange(channelPrograms.Select(program => _tvDtoService.GetProgramInfoDto(program, item)));
 
                     list.Add(item);
                 }
@@ -366,61 +260,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
         }
 
-        private async Task<IEnumerable<RecordingInfoDto>> GetRecordings(ILiveTvService service, CancellationToken cancellationToken)
-        {
-            var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
-
-            return recordings.Select(i => GetRecordingInfoDto(i, service));
-        }
-
-        private RecordingInfoDto GetRecordingInfoDto(RecordingInfo info, ILiveTvService service)
-        {
-            var id = service.Name + info.ChannelId + info.Id;
-            id = id.GetMD5().ToString("N");
-
-            var dto = new RecordingInfoDto
-            {
-                ChannelName = info.ChannelName,
-                Overview = info.Overview,
-                EndDate = info.EndDate,
-                Name = info.Name,
-                StartDate = info.StartDate,
-                Id = id,
-                ExternalId = info.Id,
-                ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
-                Status = info.Status,
-                Path = info.Path,
-                Genres = info.Genres,
-                IsRepeat = info.IsRepeat,
-                EpisodeTitle = info.EpisodeTitle,
-                ChannelType = info.ChannelType,
-                MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video,
-                CommunityRating = info.CommunityRating,
-                OfficialRating = info.OfficialRating,
-                Audio = info.Audio,
-                IsHD = info.IsHD
-            };
-
-            var duration = info.EndDate - info.StartDate;
-            dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
-
-            if (!string.IsNullOrEmpty(info.ProgramId))
-            {
-                dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N");
-            }
-
-            return dto;
-        }
-
         public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken)
         {
+            var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
+
             var list = new List<RecordingInfoDto>();
 
             if (ActiveService != null)
             {
-                var recordings = await GetRecordings(ActiveService, cancellationToken).ConfigureAwait(false);
+                var recordings = await ActiveService.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
+
+                var dtos = recordings.Select(i => _tvDtoService.GetRecordingInfoDto(i, ActiveService, user));
 
-                list.AddRange(recordings);
+                list.AddRange(dtos);
             }
 
             if (!string.IsNullOrEmpty(query.ChannelId))
@@ -471,9 +323,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             if (ActiveService != null)
             {
-                var timers = await GetTimers(ActiveService, cancellationToken).ConfigureAwait(false);
+                var timers = await ActiveService.GetTimersAsync(cancellationToken).ConfigureAwait(false);
 
-                list.AddRange(timers);
+                var dtos = timers.Select(i => _tvDtoService.GetTimerInfoDto(i, ActiveService));
+
+                list.AddRange(dtos);
             }
 
             if (!string.IsNullOrEmpty(query.ChannelId))
@@ -492,47 +346,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             };
         }
 
-        private async Task<IEnumerable<TimerInfoDto>> GetTimers(ILiveTvService service, CancellationToken cancellationToken)
-        {
-            var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false);
-
-            return timers.Select(i => GetTimerInfoDto(i, service));
-        }
-
-        private TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service)
-        {
-            var id = service.Name + info.ChannelId + info.Id;
-            id = id.GetMD5().ToString("N");
-
-            var dto = new TimerInfoDto
-            {
-                ChannelName = info.ChannelName,
-                Overview = info.Overview,
-                EndDate = info.EndDate,
-                Name = info.Name,
-                StartDate = info.StartDate,
-                Id = id,
-                ExternalId = info.Id,
-                ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
-                Status = info.Status,
-                SeriesTimerId = info.SeriesTimerId,
-                RequestedPostPaddingSeconds = info.RequestedPostPaddingSeconds,
-                RequestedPrePaddingSeconds = info.RequestedPrePaddingSeconds,
-                RequiredPostPaddingSeconds = info.RequiredPostPaddingSeconds,
-                RequiredPrePaddingSeconds = info.RequiredPrePaddingSeconds
-            };
-
-            var duration = info.EndDate - info.StartDate;
-            dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
-
-            if (!string.IsNullOrEmpty(info.ProgramId))
-            {
-                dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N");
-            }
-
-            return dto;
-        }
-
         public async Task DeleteRecording(string recordingId)
         {
             var recordings = await GetRecordings(new RecordingQuery
@@ -579,9 +392,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
         }
 
-        public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken)
+        public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken, User user = null)
         {
-            var results = await GetRecordings(new RecordingQuery(), cancellationToken).ConfigureAwait(false);
+            var results = await GetRecordings(new RecordingQuery
+            {
+                UserId = user == null ? null : user.Id.ToString("N")
+
+            }, cancellationToken).ConfigureAwait(false);
 
             return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
         }
@@ -592,5 +409,60 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
         }
+
+        public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
+        {
+            var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
+
+            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+        }
+        
+        public Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
+        {
+            var info = _tvDtoService.GetTimerInfo(timer);
+
+            return ActiveService.UpdateTimerAsync(info, cancellationToken);
+        }
+
+        public Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
+        {
+            var info = _tvDtoService.GetSeriesTimerInfo(timer);
+
+            return ActiveService.UpdateSeriesTimerAsync(info, cancellationToken);
+        }
+
+        public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
+        {
+            var list = new List<SeriesTimerInfoDto>();
+
+            if (ActiveService != null)
+            {
+                var timers = await ActiveService.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
+
+                var dtos = timers.Select(i => _tvDtoService.GetSeriesTimerInfoDto(i, ActiveService));
+
+                list.AddRange(dtos);
+            }
+
+            var returnArray = list.OrderByDescending(i => i.StartDate)
+                .ToArray();
+
+            return new QueryResult<SeriesTimerInfoDto>
+            {
+                Items = returnArray,
+                TotalRecordCount = returnArray.Length
+            };
+        }
+
+        public async Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null)
+        {
+            var results = await GetChannels(new ChannelQuery
+            {
+                UserId = user == null ? null : user.Id.ToString("N")
+
+            }, cancellationToken).ConfigureAwait(false);
+
+            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+        }
     }
 }

+ 2 - 1
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -151,6 +151,7 @@
     <Compile Include="Library\Validators\StudiosValidator.cs" />
     <Compile Include="Library\Validators\YearsPostScanTask.cs" />
     <Compile Include="LiveTv\ChannelImageProvider.cs" />
+    <Compile Include="LiveTv\LiveTvDtoService.cs" />
     <Compile Include="LiveTv\LiveTvManager.cs" />
     <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
     <Compile Include="Localization\LocalizationManager.cs" />
@@ -338,7 +339,7 @@
       <Link>swagger-ui\swagger-ui.min.js</Link>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="Localization\Ratings\be.txt" />
+    <EmbeddedResource Include="Localization\Ratings\be.txt" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />

+ 2 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -281,7 +281,7 @@ namespace MediaBrowser.ServerApplication
             DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor);
             RegisterSingleInstance(DtoService);
 
-            LiveTvManager = new LiveTvManager(ApplicationPaths, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserManager, LocalizationManager, UserDataManager, DtoService);
+            LiveTvManager = new LiveTvManager(ApplicationPaths, FileSystemManager, Logger, ItemRepository, ImageProcessor, LocalizationManager, UserDataManager, DtoService, UserManager);
             RegisterSingleInstance(LiveTvManager);
             progress.Report(15);
 
@@ -609,6 +609,7 @@ namespace MediaBrowser.ServerApplication
                 ProgramDataPath = ApplicationPaths.ProgramDataPath,
                 LogPath = ApplicationPaths.LogDirectoryPath,
                 ItemsByNamePath = ApplicationPaths.ItemsByNamePath,
+                CachePath = ApplicationPaths.CachePath,
                 MacAddress = GetMacAddress(),
                 HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber,
                 OperatingSystem = Environment.OSVersion.ToString(),

+ 2 - 0
MediaBrowser.ServerApplication/Native/ServerAuthorization.cs

@@ -20,6 +20,8 @@ namespace MediaBrowser.ServerApplication.Native
         /// <param name="tempDirectory">The temp directory.</param>
         public static void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int webSocketPort, int udpPort, string tempDirectory)
         {
+            Directory.CreateDirectory(tempDirectory);
+
             // Create a temp file path to extract the bat file to
             var tmpFile = Path.Combine(tempDirectory, Guid.NewGuid() + ".bat");
 

+ 18 - 1
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -242,7 +242,24 @@ namespace MediaBrowser.WebDashboard.Api
                 pages = pages.Where(p => p.ConfigurationPageType == request.PageType.Value);
             }
 
-            return ResultFactory.GetOptimizedResult(Request, pages.Select(p => new ConfigurationPageInfo(p)).ToList());
+            // Don't allow a failing plugin to fail them all
+            var configPages = pages.Select(p =>
+            {
+
+                try
+                {
+                    return new ConfigurationPageInfo(p);
+                }
+                catch (Exception ex)
+                {
+                    Logger.ErrorException("Error getting plugin information from {0}", ex, p.GetType().Name);
+                    return null;
+                }
+            })
+                .Where(i => i != null)
+                .ToList();
+
+            return ResultFactory.GetOptimizedResult(Request, configPages);
         }
 
         /// <summary>

+ 94 - 4
MediaBrowser.WebDashboard/ApiClient.js

@@ -506,20 +506,110 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
             });
         };
 
-        self.createLiveTvTimer = function (options) {
+        self.createLiveTvTimer = function (item) {
 
-            if (!options) {
-                throw new Error("null options");
+            if (!item) {
+                throw new Error("null item");
             }
 
-            var url = self.getUrl("LiveTv/Timers", options);
+            var url = self.getUrl("LiveTv/Timers");
 
             return self.ajax({
                 type: "POST",
+                url: url,
+                data: JSON.stringify(item),
+                contentType: "application/json"
+            });
+        };
+
+        self.updateLiveTvTimer = function (item) {
+
+            if (!item) {
+                throw new Error("null item");
+            }
+
+            var url = self.getUrl("LiveTv/Timers/" + item.Id);
+
+            return self.ajax({
+                type: "POST",
+                url: url,
+                data: JSON.stringify(item),
+                contentType: "application/json"
+            });
+        };
+
+        self.getLiveTvSeriesTimers = function (options) {
+
+            var url = self.getUrl("LiveTv/SeriesTimers", options || {});
+
+            return self.ajax({
+                type: "GET",
+                url: url,
+                dataType: "json"
+            });
+        };
+
+        self.getLiveTvSeriesTimer = function (id) {
+
+            if (!id) {
+                throw new Error("null id");
+            }
+
+            var url = self.getUrl("LiveTv/SeriesTimers/" + id);
+
+            return self.ajax({
+                type: "GET",
+                url: url,
+                dataType: "json"
+            });
+        };
+
+        self.cancelLiveTvSeriesTimer = function (id) {
+
+            if (!id) {
+                throw new Error("null id");
+            }
+
+            var url = self.getUrl("LiveTv/SeriesTimers/" + id);
+
+            return self.ajax({
+                type: "DELETE",
                 url: url
             });
         };
 
+        self.createLiveTvSeriesTimer = function (item) {
+
+            if (!item) {
+                throw new Error("null item");
+            }
+
+            var url = self.getUrl("LiveTv/SeriesTimers");
+
+            return self.ajax({
+                type: "POST",
+                url: url,
+                data: JSON.stringify(item),
+                contentType: "application/json"
+            });
+        };
+
+        self.updateLiveTvSeriesTimer = function (item) {
+
+            if (!item) {
+                throw new Error("null item");
+            }
+
+            var url = self.getUrl("LiveTv/SeriesTimers/" + item.Id);
+
+            return self.ajax({
+                type: "POST",
+                url: url,
+                data: JSON.stringify(item),
+                contentType: "application/json"
+            });
+        };
+
         /**
          * Gets the current server status
          */

+ 1 - 1
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -1232,7 +1232,7 @@
     </Content>
   </ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="packages.config" />
+    <None Include="packages.config" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>

+ 1 - 1
MediaBrowser.WebDashboard/packages.config

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.204" targetFramework="net45" />
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.206" targetFramework="net45" />
 </packages>

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.269</version>
+        <version>3.0.271</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.269" />
+            <dependency id="MediaBrowser.Common" version="3.0.271" />
             <dependency id="NLog" version="2.1.0" />
             <dependency id="SimpleInjector" version="2.4.0" />
             <dependency id="sharpcompress" version="0.10.2" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.269</version>
+        <version>3.0.271</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.269</version>
+        <version>3.0.271</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.269" />
+            <dependency id="MediaBrowser.Common" version="3.0.271" />
         </dependencies>
     </metadata>
     <files>