Pārlūkot izejas kodu

support deleting and canceling live tv recordings and timers

Luke Pulverenti 11 gadi atpakaļ
vecāks
revīzija
235b838fbe
26 mainītis faili ar 280 papildinājumiem un 81 dzēšanām
  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>