瀏覽代碼

support deleting and canceling live tv recordings and timers

Luke Pulverenti 11 年之前
父節點
當前提交
235b838fbe
共有 26 個文件被更改,包括 280 次插入81 次删除
  1. 31 0
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  2. 28 4
      MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
  3. 4 2
      MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
  4. 49 15
      MediaBrowser.Api/TvShowsService.cs
  5. 1 1
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  6. 2 1
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  7. 0 1
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  8. 5 0
      MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs
  9. 11 1
      MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs
  10. 14 0
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  11. 0 8
      MediaBrowser.Controller/LiveTv/ILiveTvService.cs
  12. 6 0
      MediaBrowser.Controller/LiveTv/RecordingInfo.cs
  13. 6 12
      MediaBrowser.Controller/LiveTv/TimerInfo.cs
  14. 7 0
      MediaBrowser.Model/LiveTv/RecordingStatus.cs
  15. 9 15
      MediaBrowser.Model/LiveTv/TimerInfoDto.cs
  16. 7 6
      MediaBrowser.Model/Querying/EpisodeQuery.cs
  17. 6 0
      MediaBrowser.Model/Tasks/TaskInfo.cs
  18. 55 2
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  19. 7 2
      MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
  20. 6 1
      MediaBrowser.ServerApplication/Native/HttpClientFactory.cs
  21. 18 2
      MediaBrowser.WebDashboard/ApiClient.js
  22. 2 2
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  23. 1 1
      MediaBrowser.WebDashboard/packages.config
  24. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  25. 1 1
      Nuget/MediaBrowser.Common.nuspec
  26. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 31 - 0
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -1,4 +1,5 @@
 using System.Threading;
+using System.Threading.Tasks;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
@@ -82,6 +83,22 @@ namespace MediaBrowser.Api.LiveTv
         public string UserId { get; set; }
     }
 
+    [Route("/LiveTv/Recordings/{Id}", "DELETE")]
+    [Api(Description = "Deletes a live tv recording")]
+    public class DeleteRecording : IReturnVoid
+    {
+        [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+    }
+
+    [Route("/LiveTv/Timers/{Id}", "DELETE")]
+    [Api(Description = "Cancels a live tv timer")]
+    public class CancelTimer : IReturnVoid
+    {
+        [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+    }
+    
     public class LiveTvService : BaseApiService
     {
         private readonly ILiveTvManager _liveTvManager;
@@ -176,5 +193,19 @@ namespace MediaBrowser.Api.LiveTv
 
             return ToOptimizedResult(result);
         }
+
+        public void Delete(DeleteRecording request)
+        {
+            var task = _liveTvManager.DeleteRecording(request.Id);
+
+            Task.WaitAll(task);
+        }
+
+        public void Delete(CancelTimer request)
+        {
+            var task = _liveTvManager.CancelTimer(request.Id);
+
+            Task.WaitAll(task);
+        }
     }
 }

+ 28 - 4
MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs

@@ -31,7 +31,8 @@ namespace MediaBrowser.Api.ScheduledTasks
     [Api(Description = "Gets scheduled tasks")]
     public class GetScheduledTasks : IReturn<List<TaskInfo>>
     {
-
+        [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsHidden { get; set; }
     }
 
     /// <summary>
@@ -112,10 +113,33 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// <returns>IEnumerable{TaskInfo}.</returns>
         public object Get(GetScheduledTasks request)
         {
-            var result = TaskManager.ScheduledTasks.OrderBy(i => i.Name)
-                         .Select(ScheduledTaskHelpers.GetTaskInfo).ToList();
+            IEnumerable<IScheduledTaskWorker> result = TaskManager.ScheduledTasks
+                .OrderBy(i => i.Name);
 
-            return ToOptimizedResult(result);
+            if (request.IsHidden.HasValue)
+            {
+                var val = request.IsHidden.Value;
+
+                result = result.Where(i =>
+                {
+                    var isHidden = false;
+
+                    var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
+
+                    if (configurableTask != null)
+                    {
+                        isHidden = configurableTask.IsHidden;
+                    }
+
+                    return isHidden == val;
+                });
+            }
+
+            var infos = result
+                .Select(ScheduledTaskHelpers.GetTaskInfo)
+                .ToList();
+
+            return ToOptimizedResult(infos);
         }
 
         /// <summary>

+ 4 - 2
MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs

@@ -46,8 +46,10 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// <returns>Task{IEnumerable{TaskInfo}}.</returns>
         protected override Task<IEnumerable<TaskInfo>> GetDataToSend(object state)
         {
-            return Task.FromResult(TaskManager.ScheduledTasks.OrderBy(i => i.Name)
-                         .Select(ScheduledTaskHelpers.GetTaskInfo));
+            return Task.FromResult(TaskManager.ScheduledTasks
+                .OrderBy(i => i.Name)
+                .Select(ScheduledTaskHelpers.GetTaskInfo)
+                .Where(i => !i.IsHidden));
         }
     }
 }

+ 49 - 15
MediaBrowser.Api/TvShowsService.cs

@@ -81,8 +81,11 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "Season", Description = "Optional filter by season number.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public int? Season { get; set; }
 
-        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string ExcludeLocationTypes { get; set; }
+        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsMissing { get; set; }
+
+        [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsVirtualUnaired { get; set; }
     }
 
     [Route("/Shows/{Id}/Seasons", "GET")]
@@ -106,11 +109,14 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         public Guid Id { get; set; }
 
-        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string ExcludeLocationTypes { get; set; }
-
         [ApiMember(Name = "IsSpecialSeason", Description = "Optional. Filter by special season.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? IsSpecialSeason { get; set; }
+
+        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsMissing { get; set; }
+
+        [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsVirtualUnaired { get; set; }
     }
 
     /// <summary>
@@ -380,12 +386,7 @@ namespace MediaBrowser.Api
                 }
             }
 
-            // ExcludeLocationTypes
-            if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
-            {
-                var vals = request.ExcludeLocationTypes.Split(',');
-                seasons = seasons.Where(f => !vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase));
-            }
+            seasons = FilterVirtualSeasons(request, seasons);
 
             seasons = _libraryManager.Sort(seasons, user, new[] { sortOrder }, SortOrder.Ascending)
                 .Cast<Season>();
@@ -400,6 +401,34 @@ namespace MediaBrowser.Api
             };
         }
 
+        private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items)
+        {
+            if (request.IsMissing.HasValue && request.IsVirtualUnaired.HasValue)
+            {
+                var isMissing = request.IsMissing.Value;
+                var isVirtualUnaired = request.IsVirtualUnaired.Value;
+
+                if (!isMissing && !isVirtualUnaired)
+                {
+                    return items.Where(i => !i.IsMissingOrVirtualUnaired);
+                }
+            }
+
+            if (request.IsMissing.HasValue)
+            {
+                var val = request.IsMissing.Value;
+                items = items.Where(i => i.IsMissingSeason == val);
+            }
+
+            if (request.IsVirtualUnaired.HasValue)
+            {
+                var val = request.IsVirtualUnaired.Value;
+                items = items.Where(i => i.IsVirtualUnaired == val);
+            }
+
+            return items;
+        }
+        
         public object Get(GetEpisodes request)
         {
             var user = _userManager.GetUserById(request.UserId);
@@ -431,11 +460,16 @@ namespace MediaBrowser.Api
                 episodes = episodes.Where(i => !i.IsVirtualUnaired);
             }
 
-            // ExcludeLocationTypes
-            if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
+            if (request.IsMissing.HasValue)
+            {
+                var val = request.IsMissing.Value;
+                episodes = episodes.Where(i => i.IsMissingEpisode == val);
+            }
+
+            if (request.IsVirtualUnaired.HasValue)
             {
-                var vals = request.ExcludeLocationTypes.Split(',');
-                episodes = episodes.Where(f => !vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase));
+                var val = request.IsVirtualUnaired.Value;
+                episodes = episodes.Where(i => i.IsVirtualUnaired == val);
             }
 
             episodes = _libraryManager.Sort(episodes, user, new[] { sortOrder }, SortOrder.Ascending)

+ 1 - 1
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -218,7 +218,7 @@ namespace MediaBrowser.Common.Implementations
             try
             {
                 // Increase the max http request limit
-                ServicePointManager.DefaultConnectionLimit = Math.Max(48, ServicePointManager.DefaultConnectionLimit);
+                ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
             }
             catch (Exception ex)
             {

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

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Configuration;
+using System.Reflection;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;

+ 0 - 1
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs

@@ -29,7 +29,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         /// <summary>
         /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
         /// </summary>
-        /// <param name="appPaths">The app paths.</param>
         public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
         {
             ApplicationPaths = appPaths;

+ 5 - 0
MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs

@@ -42,4 +42,9 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         IEnumerable<ITaskTrigger> GetDefaultTriggers();
     }
+
+    public interface IConfigurableScheduledTask
+    {
+        bool IsHidden { get; }
+    }
 }

+ 11 - 1
MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs

@@ -16,6 +16,15 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// <returns>TaskInfo.</returns>
         public static TaskInfo GetTaskInfo(IScheduledTaskWorker task)
         {
+            var isHidden = false;
+
+            var configurableTask = task.ScheduledTask as IConfigurableScheduledTask;
+
+            if (configurableTask != null)
+            {
+                isHidden = configurableTask.IsHidden;
+            }
+
             return new TaskInfo
             {
                 Name = task.Name,
@@ -25,7 +34,8 @@ namespace MediaBrowser.Common.ScheduledTasks
                 LastExecutionResult = task.LastExecutionResult,
                 Triggers = task.Triggers.Select(GetTriggerInfo).ToList(),
                 Description = task.Description,
-                Category = task.Category
+                Category = task.Category,
+                IsHidden = isHidden
             };
         }
 

+ 14 - 0
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -24,6 +24,20 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task.</returns>
         Task ScheduleRecording(string programId);
 
+        /// <summary>
+        /// Deletes the recording.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <returns>Task.</returns>
+        Task DeleteRecording(string id);
+
+        /// <summary>
+        /// Cancels the timer.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <returns>Task.</returns>
+        Task CancelTimer(string id);
+        
         /// <summary>
         /// Adds the parts.
         /// </summary>

+ 0 - 8
MediaBrowser.Controller/LiveTv/ILiveTvService.cs

@@ -47,14 +47,6 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task.</returns>
         Task CreateTimerAsync(TimerInfo 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>
         /// Gets the channel image asynchronous.
         /// </summary>

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

@@ -40,6 +40,12 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         public DateTime EndDate { 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 status.
         /// </summary>

+ 6 - 12
MediaBrowser.Controller/LiveTv/TimerInfo.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Model.LiveTv;
 using System;
-using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -21,6 +20,12 @@ namespace MediaBrowser.Controller.LiveTv
         /// </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>
         /// Name of the recording.
         /// </summary>
@@ -52,16 +57,5 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value><c>true</c> if this instance is recurring; otherwise, <c>false</c>.</value>
         public bool IsRecurring { get; set; }
-
-        /// <summary>
-        /// Gets or sets the recurring days.
-        /// </summary>
-        /// <value>The recurring days.</value>
-        public List<DayOfWeek> RecurringDays { get; set; }
-
-        public TimerInfo()
-        {
-            RecurringDays = new List<DayOfWeek>();
-        }
     }
 }

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

@@ -10,4 +10,11 @@ namespace MediaBrowser.Model.LiveTv
         Conflicted,
         Deleted
     }
+
+    public enum RecurrenceType
+    {
+        Manual,
+        NewProgramEvents,
+        AllProgramEvents
+    }
 }

+ 9 - 15
MediaBrowser.Model/LiveTv/TimerInfoDto.cs

@@ -1,11 +1,10 @@
 using System;
-using System.Collections.Generic;
 
 namespace MediaBrowser.Model.LiveTv
 {
     public class TimerInfoDto
     {
-          /// <summary>
+        /// <summary>
         /// Id of the recording.
         /// </summary>
         public string Id { get; set; }
@@ -15,7 +14,7 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         /// <value>The external identifier.</value>
         public string ExternalId { get; set; }
-        
+
         /// <summary>
         /// ChannelId of the recording.
         /// </summary>
@@ -26,6 +25,12 @@ namespace MediaBrowser.Model.LiveTv
         /// </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>
         /// Name of the recording.
         /// </summary>
@@ -57,16 +62,5 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         /// <value><c>true</c> if this instance is recurring; otherwise, <c>false</c>.</value>
         public bool IsRecurring { get; set; }
-
-        /// <summary>
-        /// Gets or sets the recurring days.
-        /// </summary>
-        /// <value>The recurring days.</value>
-        public List<DayOfWeek> RecurringDays { get; set; }
-
-        public TimerInfoDto()
-        {
-            RecurringDays = new List<DayOfWeek>();
-        }
-  }
+    }
 }

+ 7 - 6
MediaBrowser.Model/Querying/EpisodeQuery.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Entities;
-
+
 namespace MediaBrowser.Model.Querying
 {
     public class EpisodeQuery
@@ -8,7 +7,9 @@ namespace MediaBrowser.Model.Querying
 
         public string SeriesId { get; set; }
 
-        public LocationType[] ExcludeLocationTypes { get; set; }
+        public bool? IsMissing { get; set; }
+
+        public bool? IsVirtualUnaired { get; set; }
 
         public int? SeasonNumber { get; set; }
 
@@ -17,7 +18,6 @@ namespace MediaBrowser.Model.Querying
         public EpisodeQuery()
         {
             Fields = new ItemFields[] { };
-            ExcludeLocationTypes = new LocationType[] { };
         }
     }
 
@@ -27,7 +27,9 @@ namespace MediaBrowser.Model.Querying
 
         public string SeriesId { get; set; }
 
-        public LocationType[] ExcludeLocationTypes { get; set; }
+        public bool? IsMissing { get; set; }
+
+        public bool? IsVirtualUnaired { get; set; }
 
         public ItemFields[] Fields { get; set; }
 
@@ -36,7 +38,6 @@ namespace MediaBrowser.Model.Querying
         public SeasonQuery()
         {
             Fields = new ItemFields[] { };
-            ExcludeLocationTypes = new LocationType[] { };
         }
     }
 }

+ 6 - 0
MediaBrowser.Model/Tasks/TaskInfo.cs

@@ -56,6 +56,12 @@ namespace MediaBrowser.Model.Tasks
         /// <value>The category.</value>
         public string Category { get; set; }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is hidden.
+        /// </summary>
+        /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+        public bool IsHidden { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="TaskInfo"/> class.
         /// </summary>

+ 55 - 2
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -397,6 +397,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 Status = info.Status
             };
 
+            if (!string.IsNullOrEmpty(info.ProgramId))
+            {
+                dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N");
+            }
+
             return dto;
         }
 
@@ -503,13 +508,61 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 ExternalId = info.Id,
                 ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N"),
                 Status = info.Status,
-                IsRecurring = info.IsRecurring,
-                RecurringDays = info.RecurringDays
+                IsRecurring = info.IsRecurring
             };
 
+            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
+            {
+
+            }, CancellationToken.None).ConfigureAwait(false);
+
+            var recording = recordings.Items
+                .FirstOrDefault(i => string.Equals(recordingId, i.Id, StringComparison.OrdinalIgnoreCase));
+
+            if (recording == null)
+            {
+                throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
+            }
+
+            var channel = GetChannel(recording.ChannelId);
 
+            var service = GetServices(channel.ServiceName, null)
+                .First();
+
+            await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
+        }
+
+        public async Task CancelTimer(string id)
+        {
+            var timers = await GetTimers(new TimerQuery
+            {
+
+            }, CancellationToken.None).ConfigureAwait(false);
+
+            var timer = timers.Items
+                .FirstOrDefault(i => string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase));
+
+            if (timer == null)
+            {
+                throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
+            }
+
+            var channel = GetChannel(timer.ChannelId);
+
+            var service = GetServices(channel.ServiceName, null)
+                .First();
+
+            await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+        }
     }
 }

+ 7 - 2
MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Server.Implementations.LiveTv
 {
-    class RefreshChannelsScheduledTask : IScheduledTask
+    class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
     {
         private readonly ILiveTvManager _liveTvManager;
 
@@ -33,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         public Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress)
         {
-            var manager = (LiveTvManager) _liveTvManager;
+            var manager = (LiveTvManager)_liveTvManager;
 
             return manager.RefreshChannels(progress, cancellationToken);
         }
@@ -50,5 +50,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 new IntervalTrigger{ Interval = TimeSpan.FromHours(2)}
             };
         }
+
+        public bool IsHidden
+        {
+            get { return _liveTvManager.Services.Count == 0; }
+        }
     }
 }

+ 6 - 1
MediaBrowser.ServerApplication/Native/HttpClientFactory.cs

@@ -17,14 +17,19 @@ namespace MediaBrowser.ServerApplication.Native
         /// <returns>HttpClient.</returns>
         public static HttpClient GetHttpClient(bool enableHttpCompression)
         {
-            return new HttpClient(new WebRequestHandler
+            var client = new HttpClient(new WebRequestHandler
             {
                 CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate),
                 AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None
+                 
             })
             {
                 Timeout = TimeSpan.FromSeconds(20)
             };
+
+            client.DefaultRequestHeaders.Add("Connection", "Keep-Alive");
+
+            return client;
         }
     }
 }

+ 18 - 2
MediaBrowser.WebDashboard/ApiClient.js

@@ -506,6 +506,20 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
             });
         };
 
+        self.createLiveTvTimer = function (options) {
+
+            if (!options) {
+                throw new Error("null options");
+            }
+
+            var url = self.getUrl("LiveTv/Timers", options);
+
+            return self.ajax({
+                type: "POST",
+                url: url
+            });
+        };
+
         /**
          * Gets the current server status
          */
@@ -1019,9 +1033,11 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
         /**
          * Gets the server's scheduled tasks
          */
-        self.getScheduledTasks = function () {
+        self.getScheduledTasks = function (options) {
 
-            var url = self.getUrl("ScheduledTasks");
+            options = options || {};
+            
+            var url = self.getUrl("ScheduledTasks", options);
 
             return self.ajax({
                 type: "GET",

+ 2 - 2
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -284,10 +284,10 @@
     <Content Include="dashboard-ui\css\images\userdata\thumbs_up_on.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\css\images\userdata\playedoff.png">
+    <Content Include="dashboard-ui\css\images\userdata\checkedoff.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\css\images\userdata\playedon.png">
+    <Content Include="dashboard-ui\css\images\userdata\checkedon.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     <Content Include="dashboard-ui\css\librarybrowser.css">

+ 1 - 1
MediaBrowser.WebDashboard/packages.config

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.201" targetFramework="net45" />
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.203" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.62" 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.252</version>
+        <version>3.0.253</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.252" />
+            <dependency id="MediaBrowser.Common" version="3.0.253" />
             <dependency id="NLog" version="2.1.0" />
             <dependency id="ServiceStack.Text" version="3.9.58" />
             <dependency id="SimpleInjector" version="2.3.6" />

+ 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.252</version>
+        <version>3.0.253</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.252</version>
+        <version>3.0.253</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.252" />
+            <dependency id="MediaBrowser.Common" version="3.0.253" />
         </dependencies>
     </metadata>
     <files>