瀏覽代碼

Merge pull request #3 from MediaBrowser/master

Sync with Master
7illusions 11 年之前
父節點
當前提交
baf5cf2544
共有 100 個文件被更改,包括 1078 次插入519 次删除
  1. 5 31
      MediaBrowser.Api/ApiEntryPoint.cs
  2. 1 1
      MediaBrowser.Api/ChannelService.cs
  3. 13 6
      MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs
  4. 1 1
      MediaBrowser.Api/DefaultTheme/Models.cs
  5. 2 10
      MediaBrowser.Api/DisplayPreferencesService.cs
  6. 87 3
      MediaBrowser.Api/Images/ImageByNameService.cs
  7. 3 3
      MediaBrowser.Api/Images/ImageService.cs
  8. 23 4
      MediaBrowser.Api/ItemLookupService.cs
  9. 4 4
      MediaBrowser.Api/LocalizationService.cs
  10. 2 2
      MediaBrowser.Api/PackageService.cs
  11. 2 2
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  12. 1 3
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  13. 1 3
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  14. 6 8
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  15. 9 9
      MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
  16. 31 22
      MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
  17. 6 6
      MediaBrowser.Api/SearchService.cs
  18. 1 1
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  19. 3 0
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  20. 1 3
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  21. 1 1
      MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs
  22. 2 2
      MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs
  23. 2 2
      MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs
  24. 1 0
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  25. 12 6
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  26. 2 1
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  27. 21 11
      MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  28. 21 9
      MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
  29. 4 2
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  30. 0 0
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs
  31. 0 7
      MediaBrowser.Common.Implementations/Security/MBRegistration.cs
  32. 11 0
      MediaBrowser.Common.Implementations/Security/RegRecord.cs
  33. 7 5
      MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
  34. 2 13
      MediaBrowser.Common/IApplicationHost.cs
  35. 15 0
      MediaBrowser.Common/IDependencyContainer.cs
  36. 5 1
      MediaBrowser.Common/MediaBrowser.Common.csproj
  37. 84 14
      MediaBrowser.Common/Net/BasePeriodicWebSocketListener.cs
  38. 7 10
      MediaBrowser.Common/Net/HttpRequestOptions.cs
  39. 0 12
      MediaBrowser.Common/Net/IWebSocketConnection.cs
  40. 16 0
      MediaBrowser.Common/Net/WebSocketMessageInfo.cs
  41. 0 2
      MediaBrowser.Common/Plugins/BasePlugin.cs
  42. 16 0
      MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs
  43. 7 0
      MediaBrowser.Common/ScheduledTasks/IHasKey.cs
  44. 0 19
      MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs
  45. 8 2
      MediaBrowser.Common/ScheduledTasks/IScheduledTaskWorker.cs
  46. 3 4
      MediaBrowser.Common/ScheduledTasks/ITaskManager.cs
  47. 1 1
      MediaBrowser.Common/ScheduledTasks/TaskCompletionEventArgs.cs
  48. 2 2
      MediaBrowser.Common/Updates/IInstallationManager.cs
  49. 0 6
      MediaBrowser.Common/Updates/InstallationEventArgs.cs
  50. 9 0
      MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
  51. 2 0
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  52. 2 0
      MediaBrowser.Controller/Channels/ChannelCategoryItem.cs
  53. 30 0
      MediaBrowser.Controller/Channels/ChannelInfo.cs
  54. 0 41
      MediaBrowser.Controller/Channels/ChannelItemInfo.cs
  55. 12 0
      MediaBrowser.Controller/Channels/ChannelItemResult.cs
  56. 9 0
      MediaBrowser.Controller/Channels/ChannelItemType.cs
  57. 17 0
      MediaBrowser.Controller/Channels/ChannelMediaContentType.cs
  58. 26 0
      MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
  59. 9 0
      MediaBrowser.Controller/Channels/ChannelMediaType.cs
  60. 7 0
      MediaBrowser.Controller/Channels/ChannelSearchInfo.cs
  61. 2 0
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  62. 0 45
      MediaBrowser.Controller/Channels/IChannel.cs
  63. 9 0
      MediaBrowser.Controller/Channels/IChannelFactory.cs
  64. 2 7
      MediaBrowser.Controller/Channels/IChannelItem.cs
  65. 2 1
      MediaBrowser.Controller/Channels/IChannelManager.cs
  66. 9 0
      MediaBrowser.Controller/Channels/IChannelMediaItem.cs
  67. 11 0
      MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs
  68. 19 0
      MediaBrowser.Controller/Chapters/ChapterResponse.cs
  69. 29 0
      MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs
  70. 39 0
      MediaBrowser.Controller/Chapters/IChapterProvider.cs
  71. 7 0
      MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs
  72. 0 14
      MediaBrowser.Controller/Dlna/ControlRequest.cs
  73. 18 0
      MediaBrowser.Controller/Dlna/ControlResponse.cs
  74. 2 22
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  75. 25 0
      MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
  76. 1 7
      MediaBrowser.Controller/Entities/TV/Episode.cs
  77. 0 16
      MediaBrowser.Controller/Library/IIntroProvider.cs
  78. 0 16
      MediaBrowser.Controller/Library/ILibraryManager.cs
  79. 14 0
      MediaBrowser.Controller/Library/IMetadataFileSaver.cs
  80. 0 10
      MediaBrowser.Controller/Library/IMetadataSaver.cs
  81. 2 2
      MediaBrowser.Controller/Library/IUserManager.cs
  82. 19 0
      MediaBrowser.Controller/Library/IntroInfo.cs
  83. 22 0
      MediaBrowser.Controller/Library/LibraryManagerExtensions.cs
  84. 0 9
      MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs
  85. 11 0
      MediaBrowser.Controller/Library/PlaybackStopEventArgs.cs
  86. 9 0
      MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs
  87. 0 7
      MediaBrowser.Controller/LiveTv/LiveTvException.cs
  88. 0 56
      MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs
  89. 61 0
      MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs
  90. 0 0
      MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs
  91. 28 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  92. 3 1
      MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
  93. 1 1
      MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
  94. 19 1
      MediaBrowser.Controller/Providers/DirectoryService.cs
  95. 19 0
      MediaBrowser.Controller/Providers/VideoContentType.cs
  96. 20 0
      MediaBrowser.Controller/Security/IEncryptionManager.cs
  97. 0 8
      MediaBrowser.Controller/Session/ISessionController.cs
  98. 50 0
      MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
  99. 39 0
      MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs
  100. 11 0
      MediaBrowser.Controller/Subtitles/SubtitleResponse.cs

+ 5 - 31
MediaBrowser.Api/ApiEntryPoint.cs

@@ -71,8 +71,7 @@ namespace MediaBrowser.Api
         /// </summary>
         private void DeleteEncodedMediaCache()
         {
-            foreach (var file in Directory.EnumerateFiles(_appPaths.TranscodingTempPath)
-                .Where(i => EntityResolutionHelper.VideoFileExtensions.Contains(Path.GetExtension(i)))
+            foreach (var file in Directory.EnumerateFiles(_appPaths.TranscodingTempPath, "*", SearchOption.AllDirectories)
                 .ToList())
             {
                 File.Delete(file);
@@ -116,11 +115,10 @@ namespace MediaBrowser.Api
         /// <param name="path">The path.</param>
         /// <param name="type">The type.</param>
         /// <param name="process">The process.</param>
-        /// <param name="isVideo">if set to <c>true</c> [is video].</param>
         /// <param name="startTimeTicks">The start time ticks.</param>
         /// <param name="sourcePath">The source path.</param>
         /// <param name="deviceId">The device id.</param>
-        public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, bool isVideo, long? startTimeTicks, string sourcePath, string deviceId)
+        public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId)
         {
             lock (_activeTranscodingJobs)
             {
@@ -130,7 +128,6 @@ namespace MediaBrowser.Api
                     Path = path,
                     Process = process,
                     ActiveRequestCount = 1,
-                    IsVideo = isVideo,
                     StartTimeTicks = startTimeTicks,
                     SourcePath = sourcePath,
                     DeviceId = deviceId
@@ -261,7 +258,7 @@ namespace MediaBrowser.Api
             {
                 // This is really only needed for HLS. 
                 // Progressive streams can stop on their own reliably
-                jobs.AddRange(_activeTranscodingJobs.Where(i => isVideo == i.IsVideo && string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase)));
+                jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase)));
             }
 
             foreach (var job in jobs)
@@ -325,37 +322,15 @@ namespace MediaBrowser.Api
                 }
             }
 
-            // Determine if it exited successfully
-            var hasExitedSuccessfully = false;
-
-            try
-            {
-                hasExitedSuccessfully = process.ExitCode == 0;
-            }
-            catch (InvalidOperationException)
-            {
-
-            }
-            catch (NotSupportedException)
-            {
-
-            }
-
             // Dispose the process
             process.Dispose();
 
-            // If it didn't complete successfully cleanup the partial files
-            // Also don't cache output from resume points
-            // Also don't cache video
-            if (!hasExitedSuccessfully || job.StartTimeTicks.HasValue || job.IsVideo)
-            {
-                DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
-            }
+            DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
         }
 
         private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
         {
-            if (retryCount >= 5)
+            if (retryCount >= 10)
             {
                 return;
             }
@@ -455,7 +430,6 @@ namespace MediaBrowser.Api
         /// <value>The kill timer.</value>
         public Timer KillTimer { get; set; }
 
-        public bool IsVideo { get; set; }
         public long? StartTimeTicks { get; set; }
         public string SourcePath { get; set; }
         public string DeviceId { get; set; }

+ 1 - 1
MediaBrowser.Api/ChannelService.cs

@@ -67,7 +67,7 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public SortOrder? SortOrder { get; set; }
 
-        [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsRecentlyAdded, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Filters { get; set; }
 
         [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]

+ 13 - 6
MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs

@@ -24,6 +24,8 @@ namespace MediaBrowser.Api.DefaultTheme
 
         [ApiMember(Name = "RecentlyPlayedGamesLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int RecentlyPlayedGamesLimit { get; set; }
+
+        public string ParentId { get; set; }
     }
 
     [Route("/MBT/DefaultTheme/TV", "GET")]
@@ -49,6 +51,8 @@ namespace MediaBrowser.Api.DefaultTheme
 
         [ApiMember(Name = "LatestEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int LatestEpisodeLimit { get; set; }
+
+        public string ParentId { get; set; }
     }
 
     [Route("/MBT/DefaultTheme/Movies", "GET")]
@@ -71,6 +75,8 @@ namespace MediaBrowser.Api.DefaultTheme
 
         [ApiMember(Name = "LatestTrailersLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int LatestTrailersLimit { get; set; }
+
+        public string ParentId { get; set; }
     }
 
     [Route("/MBT/DefaultTheme/Favorites", "GET")]
@@ -224,7 +230,7 @@ namespace MediaBrowser.Api.DefaultTheme
         {
             var user = _userManager.GetUserById(request.UserId);
 
-            var items = user.RootFolder.GetRecursiveChildren(user, i => i is Game || i is GameSystem)
+            var items = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId).Where(i => i is Game || i is GameSystem)
                 .ToList();
 
             var gamesWithImages = items.OfType<Game>().Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)).ToList();
@@ -280,7 +286,7 @@ namespace MediaBrowser.Api.DefaultTheme
 
             var user = _userManager.GetUserById(request.UserId);
 
-            var series = user.RootFolder.GetRecursiveChildren(user)
+            var series = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId)
                 .OfType<Series>()
                 .ToList();
 
@@ -403,7 +409,8 @@ namespace MediaBrowser.Api.DefaultTheme
         {
             var user = _userManager.GetUserById(request.UserId);
 
-            var items = user.RootFolder.GetRecursiveChildren(user, i => i is Movie || i is Trailer || i is BoxSet)
+            var items = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId)
+                .Where(i => i is Movie || i is Trailer || i is BoxSet)
                 .ToList();
 
             var view = new MoviesView();
@@ -556,7 +563,7 @@ namespace MediaBrowser.Api.DefaultTheme
 
             // Avoid implicitly captured closure
             var currentUserId1 = user.Id;
-            
+
             view.LatestMovies = movies
                 .OrderByDescending(i => i.DateCreated)
                 .Where(i => !_userDataManager.GetUserData(currentUserId1, i.GetUserDataKey()).Played)
@@ -622,9 +629,9 @@ namespace MediaBrowser.Api.DefaultTheme
             {
                 var tag = _imageProcessor.GetImageCacheTag(item, imageType);
 
-                if (tag.HasValue)
+                if (tag != null)
                 {
-                    stub.ImageTag = tag.Value;
+                    stub.ImageTag = tag;
                 }
             }
             catch (Exception ex)

+ 1 - 1
MediaBrowser.Api/DefaultTheme/Models.cs

@@ -9,7 +9,7 @@ namespace MediaBrowser.Api.DefaultTheme
     {
         public string Name { get; set; }
         public string Id { get; set; }
-        public Guid ImageTag { get; set; }
+        public string ImageTag { get; set; }
         public ImageType ImageType { get; set; }
     }
 

+ 2 - 10
MediaBrowser.Api/DisplayPreferencesService.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Serialization;
 using ServiceStack;
@@ -77,14 +76,7 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public object Get(GetDisplayPreferences request)
         {
-            Guid displayPreferencesId;
-
-            if (!Guid.TryParse(request.Id, out displayPreferencesId))
-            {
-                displayPreferencesId = request.Id.GetMD5();
-            }
-
-            var result = _displayPreferencesManager.GetDisplayPreferences(displayPreferencesId, request.UserId, request.Client);
+            var result = _displayPreferencesManager.GetDisplayPreferences(request.Id, request.UserId, request.Client);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }

+ 87 - 3
MediaBrowser.Api/Images/ImageByNameService.cs

@@ -1,8 +1,10 @@
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using ServiceStack;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 
@@ -70,6 +72,32 @@ namespace MediaBrowser.Api.Images
         public string Theme { get; set; }
     }
 
+    [Route("/Images/MediaInfo", "GET")]
+    [Api(Description = "Gets all media info image by name")]
+    public class GetMediaInfoImages : IReturn<List<ImageByNameInfo>>
+    {
+    }
+
+    [Route("/Images/Ratings", "GET")]
+    [Api(Description = "Gets all rating images by name")]
+    public class GetRatingImages : IReturn<List<ImageByNameInfo>>
+    {
+    }
+
+    [Route("/Images/General", "GET")]
+    [Api(Description = "Gets all general images by name")]
+    public class GetGeneralImages : IReturn<List<ImageByNameInfo>>
+    {
+    }
+
+    public class ImageByNameInfo
+    {
+        public string Name { get; set; }
+        public string Theme { get; set; }
+        public long FileLength { get; set; }
+        public string Format { get; set; }
+    }
+
     /// <summary>
     /// Class ImageByNameService
     /// </summary>
@@ -89,6 +117,60 @@ namespace MediaBrowser.Api.Images
             _appPaths = appPaths;
         }
 
+        public object Get(GetMediaInfoImages request)
+        {
+            return ToOptimizedResult(GetImageList(_appPaths.MediaInfoImagesPath));
+        }
+
+        public object Get(GetRatingImages request)
+        {
+            return ToOptimizedResult(GetImageList(_appPaths.RatingsPath));
+        }
+
+        public object Get(GetGeneralImages request)
+        {
+            return ToOptimizedResult(GetImageList(_appPaths.GeneralPath));
+        }
+
+        private List<ImageByNameInfo> GetImageList(string path)
+        {
+            try
+            {
+                return new DirectoryInfo(path)
+                    .GetFiles("*", SearchOption.AllDirectories)
+                    .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.Ordinal))
+                    .Select(i => new ImageByNameInfo
+                    {
+                        Name = Path.GetFileNameWithoutExtension(i.FullName),
+                        FileLength = i.Length,
+                        Theme = GetThemeName(i.FullName, path),
+                        Format = i.Extension.ToLower().TrimStart('.')
+                    })
+                    .OrderBy(i => i.Name)
+                    .ToList();
+            }
+            catch (DirectoryNotFoundException)
+            {
+                return new List<ImageByNameInfo>();
+            }
+        }
+
+        private string GetThemeName(string path, string rootImagePath)
+        {
+            var parentName = Path.GetDirectoryName(path);
+
+            if (string.Equals(parentName, rootImagePath, StringComparison.OrdinalIgnoreCase))
+            {
+                return null;
+            }
+
+            parentName = Path.GetFileName(parentName);
+
+            return string.Equals(parentName, "all", StringComparison.OrdinalIgnoreCase) ?
+                null :
+                parentName;
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -118,7 +200,8 @@ namespace MediaBrowser.Api.Images
 
             if (Directory.Exists(themeFolder))
             {
-                var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, request.Name + i))
+                var path = BaseItem.SupportedImageExtensions
+                    .Select(i => Path.Combine(themeFolder, request.Name + i))
                     .FirstOrDefault(File.Exists);
 
                 if (!string.IsNullOrEmpty(path))
@@ -134,7 +217,8 @@ namespace MediaBrowser.Api.Images
                 // Avoid implicitly captured closure
                 var currentRequest = request;
 
-                var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, currentRequest.Name + i))
+                var path = BaseItem.SupportedImageExtensions
+                    .Select(i => Path.Combine(allFolder, currentRequest.Name + i))
                     .FirstOrDefault(File.Exists);
 
                 if (!string.IsNullOrEmpty(path))
@@ -175,7 +259,7 @@ namespace MediaBrowser.Api.Images
 
                 var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, currentRequest.Name + i))
                     .FirstOrDefault(File.Exists);
-                
+
                 if (!string.IsNullOrEmpty(path))
                 {
                     return ToStaticFileResult(path);

+ 3 - 3
MediaBrowser.Api/Images/ImageService.cs

@@ -1,5 +1,4 @@
-using System.Globalization;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
@@ -14,6 +13,7 @@ using ServiceStack.Text.Controller;
 using ServiceStack.Web;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Threading;
@@ -548,7 +548,7 @@ namespace MediaBrowser.Api.Images
 
             var contentType = GetMimeType(request.Format, imageInfo.Path);
 
-            var cacheGuid = _imageProcessor.GetImageCacheTag(item, request.Type, imageInfo.Path, originalFileImageDateModified, supportedImageEnhancers);
+            var cacheGuid = new Guid(_imageProcessor.GetImageCacheTag(item, request.Type, imageInfo.Path, originalFileImageDateModified, supportedImageEnhancers));
 
             TimeSpan? cacheDuration = null;
 

+ 23 - 4
MediaBrowser.Api/ItemLookupService.cs

@@ -1,13 +1,13 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller;
-using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Subtitles;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
 using ServiceStack;
@@ -32,6 +32,16 @@ namespace MediaBrowser.Api
         public string Id { get; set; }
     }
 
+    [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
+    public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
+    {
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+
+        [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Language { get; set; }
+    }
+
     [Route("/Items/RemoteSearch/Movie", "POST")]
     [Api(Description = "Gets external id infos for an item")]
     public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
@@ -107,19 +117,28 @@ namespace MediaBrowser.Api
 
     public class ItemLookupService : BaseApiService
     {
-        private readonly IDtoService _dtoService;
         private readonly IProviderManager _providerManager;
         private readonly IServerApplicationPaths _appPaths;
         private readonly IFileSystem _fileSystem;
         private readonly ILibraryManager _libraryManager;
+        private readonly ISubtitleManager _subtitleManager;
 
-        public ItemLookupService(IDtoService dtoService, IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager)
+        public ItemLookupService(IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager, ISubtitleManager subtitleManager)
         {
-            _dtoService = dtoService;
             _providerManager = providerManager;
             _appPaths = appPaths;
             _fileSystem = fileSystem;
             _libraryManager = libraryManager;
+            _subtitleManager = subtitleManager;
+        }
+
+        public object Get(SearchRemoteSubtitles request)
+        {
+            var video = (Video)_libraryManager.GetItemById(request.Id);
+
+            var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
+
+            return ToOptimizedResult(response);
         }
 
         public object Get(GetExternalIdInfos request)

+ 4 - 4
MediaBrowser.Api/LocalizationService.cs

@@ -67,14 +67,14 @@ namespace MediaBrowser.Api
         {
             var result = _localization.GetParentalRatings().ToList();
 
-            return ToOptimizedSerializedResultUsingCache(result);
+            return ToOptimizedResult(result);
         }
 
         public object Get(GetLocalizationOptions request)
         {
             var result = _localization.GetLocalizationOptions().ToList();
 
-            return ToOptimizedSerializedResultUsingCache(result);
+            return ToOptimizedResult(result);
         }
 
         /// <summary>
@@ -86,7 +86,7 @@ namespace MediaBrowser.Api
         {
             var result = _localization.GetCountries().ToList();
 
-            return ToOptimizedSerializedResultUsingCache(result);
+            return ToOptimizedResult(result);
         }
 
         /// <summary>
@@ -98,7 +98,7 @@ namespace MediaBrowser.Api
         {
             var result = _localization.GetCultures().ToList();
 
-            return ToOptimizedSerializedResultUsingCache(result);
+            return ToOptimizedResult(result);
         }
     }
 

+ 2 - 2
MediaBrowser.Api/PackageService.cs

@@ -112,7 +112,7 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <value>The id.</value>
         [ApiMember(Name = "Id", Description = "Installation Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
     }
 
     /// <summary>
@@ -221,7 +221,7 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public void Delete(CancelPackageInstallation request)
         {
-            var info = _installationManager.CurrentInstallations.FirstOrDefault(i => i.Item1.Id == request.Id);
+            var info = _installationManager.CurrentInstallations.FirstOrDefault(i => string.Equals(i.Item1.Id, request.Id));
 
             if (info != null)
             {

+ 2 - 2
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -542,7 +542,7 @@ namespace MediaBrowser.Api.Playback
                 var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
 
                 return isH264Output ?
-                    string.Format("{3} -vf \"{0}scale=min(iw\\,{1}):trunc(ow/a/2)*2{2}\"", yadifParam, maxWidthParam, assSubtitleParam, copyTsParam) :
+                    string.Format("{3} -vf \"{0}scale=min(iw\\,{1}):trunc(ow/dar/2)*2{2}\"", yadifParam, maxWidthParam, assSubtitleParam, copyTsParam) :
                     string.Format("{3} -vf \"{0}scale=min(iw\\,{1}):-1{2}\"", yadifParam, maxWidthParam, assSubtitleParam, copyTsParam);
             }
 
@@ -903,7 +903,7 @@ namespace MediaBrowser.Api.Playback
                 EnableRaisingEvents = true
             };
 
-            ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.IsInputVideo, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
+            ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
 
             var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
             Logger.Info(commandLineLogMessage);

+ 1 - 3
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -273,9 +273,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
 
-            var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal &&
-                                 (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
-                                  state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
+            var hasGraphicalSubs = state.SubtitleStream != null && state.SubtitleStream.IsGraphicalSubtitleStream;
 
             var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
 

+ 1 - 3
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -168,9 +168,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+1))" : 
                 " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
 
-            var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal &&
-                                 (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
-                                  state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
+            var hasGraphicalSubs = state.SubtitleStream != null && state.SubtitleStream.IsGraphicalSubtitleStream;
 
             var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
 

+ 6 - 8
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -131,24 +131,22 @@ namespace MediaBrowser.Api.Playback.Progressive
         {
             var args = "-vcodec " + codec;
 
-            // See if we can save come cpu cycles by avoiding encoding
-            if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
+            if (state.EnableMpegtsM2TsMode)
             {
-                return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf h264_mp4toannexb" : args;
+                args += " -mpegts_m2ts_mode 1";
             }
 
-            if (state.EnableMpegtsM2TsMode)
+            // See if we can save come cpu cycles by avoiding encoding
+            if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
-                args += " -mpegts_m2ts_mode 1";
+                return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf h264_mp4toannexb" : args;
             }
 
             const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
 
             args += keyFrameArg;
 
-            var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal &&
-                                   (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
-                                    state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
+            var hasGraphicalSubs = state.SubtitleStream != null && state.SubtitleStream.IsGraphicalSubtitleStream;
 
             var request = state.VideoRequest;
 

+ 9 - 9
MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// </summary>
         /// <value>The id.</value>
         [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
     }
 
     /// <summary>
@@ -44,7 +44,7 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// </summary>
         /// <value>The id.</value>
         [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
     }
 
     /// <summary>
@@ -58,7 +58,7 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// </summary>
         /// <value>The id.</value>
         [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
     }
 
     /// <summary>
@@ -72,7 +72,7 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// </summary>
         /// <value>The task id.</value>
         [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
     }
 
     /// <summary>
@@ -145,7 +145,7 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// <exception cref="MediaBrowser.Common.Extensions.ResourceNotFoundException">Task not found</exception>
         public object Get(GetScheduledTask request)
         {
-            var task = TaskManager.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id);
+            var task = TaskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
 
             if (task == null)
             {
@@ -164,7 +164,7 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// <exception cref="MediaBrowser.Common.Extensions.ResourceNotFoundException">Task not found</exception>
         public void Post(StartScheduledTask request)
         {
-            var task = TaskManager.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id);
+            var task = TaskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
 
             if (task == null)
             {
@@ -181,7 +181,7 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// <exception cref="MediaBrowser.Common.Extensions.ResourceNotFoundException">Task not found</exception>
         public void Delete(StopScheduledTask request)
         {
-            var task = TaskManager.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id);
+            var task = TaskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
 
             if (task == null)
             {
@@ -201,9 +201,9 @@ namespace MediaBrowser.Api.ScheduledTasks
             // We need to parse this manually because we told service stack not to with IRequiresRequestStream
             // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
             var pathInfo = PathInfo.Parse(Request.PathInfo);
-            var id = new Guid(pathInfo.GetArgumentValue<string>(1));
+            var id = pathInfo.GetArgumentValue<string>(1);
 
-            var task = TaskManager.ScheduledTasks.FirstOrDefault(i => i.Id == id);
+            var task = TaskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id));
 
             if (task == null)
             {

+ 31 - 22
MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Tasks;
 using System.Collections.Generic;
@@ -11,7 +12,7 @@ namespace MediaBrowser.Api.ScheduledTasks
     /// <summary>
     /// Class ScheduledTasksWebSocketListener
     /// </summary>
-    public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, object>
+    public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, WebSocketListenerState>
     {
         /// <summary>
         /// Gets or sets the task manager.
@@ -37,39 +38,47 @@ namespace MediaBrowser.Api.ScheduledTasks
             : base(logger)
         {
             TaskManager = taskManager;
+
+            TaskManager.TaskExecuting += TaskManager_TaskExecuting;
+            TaskManager.TaskCompleted += TaskManager_TaskCompleted;
+        }
+
+        void TaskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
+        {
+            SendData(true);
+            e.Task.TaskProgress -= Argument_TaskProgress;
         }
 
-        private bool _lastResponseHadTasksRunning = true;
+        void TaskManager_TaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e)
+        {
+            SendData(true);
+            e.Argument.TaskProgress += Argument_TaskProgress;
+        }
+
+        void Argument_TaskProgress(object sender, GenericEventArgs<double> e)
+        {
+            SendData(false);
+        }
 
         /// <summary>
         /// Gets the data to send.
         /// </summary>
         /// <param name="state">The state.</param>
         /// <returns>Task{IEnumerable{TaskInfo}}.</returns>
-        protected override Task<IEnumerable<TaskInfo>> GetDataToSend(object state)
+        protected override Task<IEnumerable<TaskInfo>> GetDataToSend(WebSocketListenerState state)
         {
-            var tasks = TaskManager.ScheduledTasks.ToList();
-
-            var anyRunning = tasks.Any(i => i.State != TaskState.Idle);
-
-            if (anyRunning)
-            {
-                _lastResponseHadTasksRunning = true;
-            }
-            else
-            {
-                if (!_lastResponseHadTasksRunning)
-                {
-                    return Task.FromResult<IEnumerable<TaskInfo>>(null);
-                }
-
-                _lastResponseHadTasksRunning = false;
-            }
-
-            return Task.FromResult(tasks
+            return Task.FromResult(TaskManager.ScheduledTasks
                 .OrderBy(i => i.Name)
                 .Select(ScheduledTaskHelpers.GetTaskInfo)
                 .Where(i => !i.IsHidden));
         }
+
+        protected override bool SendOnTimer
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

+ 6 - 6
MediaBrowser.Api/SearchService.cs

@@ -171,9 +171,9 @@ namespace MediaBrowser.Api
 
             var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
 
-            if (primaryImageTag.HasValue)
+            if (primaryImageTag != null)
             {
-                result.PrimaryImageTag = primaryImageTag.Value;
+                result.PrimaryImageTag = primaryImageTag;
             }
 
             SetThumbImageInfo(result, item);
@@ -250,9 +250,9 @@ namespace MediaBrowser.Api
             {
                 var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Thumb);
 
-                if (tag.HasValue)
+                if (tag != null)
                 {
-                    hint.ThumbImageTag = tag.Value;
+                    hint.ThumbImageTag = tag;
                     hint.ThumbImageItemId = itemWithImage.Id.ToString("N");
                 }
             }
@@ -271,9 +271,9 @@ namespace MediaBrowser.Api
             {
                 var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Backdrop);
 
-                if (tag.HasValue)
+                if (tag != null)
                 {
-                    hint.BackdropImageTag = tag.Value;
+                    hint.BackdropImageTag = tag;
                     hint.BackdropImageItemId = itemWithImage.Id.ToString("N");
                 }
             }

+ 1 - 1
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -69,7 +69,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// Filters to apply to the results
         /// </summary>
         /// <value>The filters.</value>
-        [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsRecentlyAdded, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Filters { get; set; }
 
         /// <summary>

+ 3 - 0
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -521,6 +521,9 @@ namespace MediaBrowser.Api.UserLibrary
 
                 case ItemFilter.IsNotFolder:
                     return items.Where(item => !item.IsFolder);
+
+                case ItemFilter.IsRecentlyAdded:
+                    return items.Where(item => (DateTime.UtcNow - item.DateCreated).TotalDays <= 10);
             }
 
             return items;

+ 1 - 3
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
@@ -8,7 +7,6 @@ using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Session;
 using ServiceStack;
 using System;

+ 1 - 1
MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs

@@ -133,7 +133,7 @@ namespace MediaBrowser.Api.WebSocket
     /// <summary>
     /// Class LogFileWebSocketState
     /// </summary>
-    public class LogFileWebSocketState
+    public class LogFileWebSocketState : WebSocketListenerState
     {
         /// <summary>
         /// Gets or sets the last log file path.

+ 2 - 2
MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs

@@ -11,7 +11,7 @@ namespace MediaBrowser.Api.WebSocket
     /// <summary>
     /// Class SessionInfoWebSocketListener
     /// </summary>
-    class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfoDto>, object>
+    class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfoDto>, WebSocketListenerState>
     {
         /// <summary>
         /// Gets the name.
@@ -43,7 +43,7 @@ namespace MediaBrowser.Api.WebSocket
         /// </summary>
         /// <param name="state">The state.</param>
         /// <returns>Task{SystemInfo}.</returns>
-        protected override Task<IEnumerable<SessionInfoDto>> GetDataToSend(object state)
+        protected override Task<IEnumerable<SessionInfoDto>> GetDataToSend(WebSocketListenerState state)
         {
             return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto));
         }

+ 2 - 2
MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs

@@ -9,7 +9,7 @@ namespace MediaBrowser.Api.WebSocket
     /// <summary>
     /// Class SystemInfoWebSocketListener
     /// </summary>
-    public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener<SystemInfo, object>
+    public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener<SystemInfo, WebSocketListenerState>
     {
         /// <summary>
         /// Gets the name.
@@ -41,7 +41,7 @@ namespace MediaBrowser.Api.WebSocket
         /// </summary>
         /// <param name="state">The state.</param>
         /// <returns>Task{SystemInfo}.</returns>
-        protected override Task<SystemInfo> GetDataToSend(object state)
+        protected override Task<SystemInfo> GetDataToSend(WebSocketListenerState state)
         {
             return Task.FromResult(_appHost.GetSystemInfo());
         }

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

@@ -13,6 +13,7 @@ using MediaBrowser.Common.Progress;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.Security;
 using MediaBrowser.Common.Updates;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;

+ 12 - 6
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -114,9 +114,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
 
-            request.CachePolicy = options.CachePolicy == Net.HttpRequestCachePolicy.None ?
-                new RequestCachePolicy(RequestCacheLevel.BypassCache) :
-                new RequestCachePolicy(RequestCacheLevel.Revalidate);
+            request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
 
             request.ConnectionGroupName = GetHostFromUrl(options.Url);
             request.KeepAlive = true;
@@ -124,6 +122,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
             request.Pipelined = true;
             request.Timeout = 20000;
 
+            if (!string.IsNullOrEmpty(options.Host))
+            {
+                request.Host = options.Host;
+            }
+
 #if !__MonoCS__
             // This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest
             // May need to remove this for mono
@@ -230,12 +233,15 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             var httpWebRequest = GetRequest(options, httpMethod, options.EnableHttpCompression);
 
-            if (!string.IsNullOrEmpty(options.RequestContent) || string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
+            if (options.RequestContentBytes != null ||
+                !string.IsNullOrEmpty(options.RequestContent) ||
+                string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
             {
-                var content = options.RequestContent ?? string.Empty;
-                var bytes = Encoding.UTF8.GetBytes(content);
+                var bytes = options.RequestContentBytes ?? 
+                    Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
 
                 httpWebRequest.ContentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
+                
                 httpWebRequest.ContentLength = bytes.Length;
                 httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
             }

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

@@ -95,11 +95,12 @@
     <Compile Include="ScheduledTasks\Tasks\DeleteCacheFileTask.cs" />
     <Compile Include="ScheduledTasks\Tasks\DeleteLogFileTask.cs" />
     <Compile Include="ScheduledTasks\Tasks\PluginUpdateTask.cs" />
-    <Compile Include="ScheduledTasks\Tasks\ReloadLoggerTask.cs" />
+    <Compile Include="ScheduledTasks\Tasks\ReloadLoggerFileTask.cs" />
     <Compile Include="ScheduledTasks\Tasks\SystemUpdateTask.cs" />
     <Compile Include="Security\MBLicenseFile.cs" />
     <Compile Include="Security\MBRegistration.cs" />
     <Compile Include="Security\PluginSecurityManager.cs" />
+    <Compile Include="Security\RegRecord.cs" />
     <Compile Include="Serialization\JsonSerializer.cs" />
     <Compile Include="Serialization\XmlSerializer.cs" />
     <Compile Include="Updates\InstallationManager.cs" />

+ 21 - 11
MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -1,6 +1,8 @@
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Tasks;
@@ -18,6 +20,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
     /// </summary>
     public class ScheduledTaskWorker : IScheduledTaskWorker
     {
+        public event EventHandler<GenericEventArgs<double>> TaskProgress;
+
         /// <summary>
         /// Gets or sets the scheduled task.
         /// </summary>
@@ -269,22 +273,22 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <summary>
         /// The _id
         /// </summary>
-        private Guid? _id;
+        private string _id;
 
         /// <summary>
         /// Gets the unique id.
         /// </summary>
         /// <value>The unique id.</value>
-        public Guid Id
+        public string Id
         {
             get
             {
-                if (!_id.HasValue)
+                if (_id == null)
                 {
-                    _id = ScheduledTask.GetType().FullName.GetMD5();
+                    _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N");
                 }
 
-                return _id.Value;
+                return _id;
             }
         }
 
@@ -344,13 +348,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
                 throw new InvalidOperationException("Cannot execute a Task that is already running");
             }
 
+            var progress = new Progress<double>();
+
             CurrentCancellationTokenSource = new CancellationTokenSource();
 
             Logger.Info("Executing {0}", Name);
 
-            ((TaskManager)TaskManager).OnTaskExecuting(ScheduledTask);
-            
-            var progress = new Progress<double>();
+            ((TaskManager)TaskManager).OnTaskExecuting(this);
 
             progress.ProgressChanged += progress_ProgressChanged;
 
@@ -412,6 +416,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         void progress_ProgressChanged(object sender, double e)
         {
             CurrentProgress = e;
+
+            EventHelper.FireEventIfNotNull(TaskProgress, this, new GenericEventArgs<double>
+            {
+                Argument = e
+
+            }, Logger);
         }
 
         /// <summary>
@@ -464,7 +474,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <value>The history file path.</value>
         private string GetHistoryFilePath()
         {
-            return Path.Combine(GetScheduledTasksDataDirectory(), Id + ".js");
+            return Path.Combine(GetScheduledTasksDataDirectory(), new Guid(Id) + ".js");
         }
 
         /// <summary>
@@ -473,7 +483,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <returns>System.String.</returns>
         private string GetConfigurationFilePath()
         {
-            return Path.Combine(GetScheduledTasksConfigurationDirectory(), Id + ".js");
+            return Path.Combine(GetScheduledTasksConfigurationDirectory(), new Guid(Id) + ".js");
         }
 
         /// <summary>
@@ -546,7 +556,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
 
             LastExecutionResult = result;
 
-            ((TaskManager) TaskManager).OnTaskCompleted(ScheduledTask, result);
+            ((TaskManager)TaskManager).OnTaskCompleted(this, result);
         }
 
         /// <summary>

+ 21 - 9
MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Tasks;
@@ -16,8 +17,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
     /// </summary>
     public class TaskManager : ITaskManager
     {
-        public event EventHandler<EventArgs> TaskExecuting;
-        public event EventHandler<GenericEventArgs<TaskResult>> TaskCompleted;
+        public event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting;
+        public event EventHandler<TaskCompletionEventArgs> TaskCompleted;
 
         /// <summary>
         /// Gets the list of Scheduled Tasks
@@ -124,7 +125,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
                 // If it's idle just execute immediately
                 if (task.State == TaskState.Idle)
                 {
-                    ((ScheduledTaskWorker)task).Execute();
+                    Execute(task);
                     return;
                 }
 
@@ -148,7 +149,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         {
             var myTasks = ScheduledTasks.ToList();
 
-            myTasks.AddRange(tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger)));
+            var list = tasks.ToList();
+            myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger)));
 
             ScheduledTasks = myTasks.ToArray();
         }
@@ -188,9 +190,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// Called when [task executing].
         /// </summary>
         /// <param name="task">The task.</param>
-        internal void OnTaskExecuting(IScheduledTask task)
+        internal void OnTaskExecuting(IScheduledTaskWorker task)
         {
-            EventHelper.QueueEventIfNotNull(TaskExecuting, task, EventArgs.Empty, Logger);
+            EventHelper.QueueEventIfNotNull(TaskExecuting, this, new GenericEventArgs<IScheduledTaskWorker>
+            {
+                Argument = task
+
+            }, Logger);
         }
 
         /// <summary>
@@ -198,9 +204,15 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// </summary>
         /// <param name="task">The task.</param>
         /// <param name="result">The result.</param>
-        internal void OnTaskCompleted(IScheduledTask task, TaskResult result)
+        internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result)
         {
-            EventHelper.QueueEventIfNotNull(TaskCompleted, task, new GenericEventArgs<TaskResult> { Argument = result }, Logger);
+            EventHelper.QueueEventIfNotNull(TaskCompleted, task, new TaskCompletionEventArgs
+            {
+                Result = result,
+                Task = task
+
+            }, Logger);
+
             ExecuteQueuedTasks();
         }
 
@@ -218,7 +230,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
 
                     if (scheduledTask.State == TaskState.Idle)
                     {
-                        ((ScheduledTaskWorker)scheduledTask).Execute();
+                        Execute(scheduledTask);
 
                         _taskQueue.Remove(type);
                     }

+ 4 - 2
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs

@@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         private readonly ILogger _logger;
 
         private readonly IFileSystem _fileSystem;
-        
+
         /// <summary>
         /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
         /// </summary>
@@ -74,9 +74,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
 
             progress.Report(90);
 
+            minDateModified = DateTime.UtcNow.AddDays(-3);
+
             try
             {
-                DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, DateTime.MaxValue, progress);
+                DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress);
             }
             catch (DirectoryNotFoundException)
             {

+ 0 - 0
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs → MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs


+ 0 - 7
MediaBrowser.Common.Implementations/Security/MBRegistration.cs

@@ -94,11 +94,4 @@ namespace MediaBrowser.Common.Implementations.Security
             return new MBRegistrationRecord { IsRegistered = reg.registered, ExpirationDate = reg.expDate, RegChecked = true };
         }
     }
-
-    class RegRecord
-    {
-        public string featId { get; set; }
-        public bool registered { get; set; }
-        public DateTime expDate { get; set; }
-    }
 }

+ 11 - 0
MediaBrowser.Common.Implementations/Security/RegRecord.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace MediaBrowser.Common.Implementations.Security
+{
+    class RegRecord
+    {
+        public string featId { get; set; }
+        public bool registered { get; set; }
+        public DateTime expDate { get; set; }
+    }
+}

+ 7 - 5
MediaBrowser.Common.Implementations/Updates/InstallationManager.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Common.Security;
 using MediaBrowser.Common.Updates;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Updates;
@@ -328,14 +329,14 @@ namespace MediaBrowser.Common.Implementations.Updates
             if (withAutoUpdateEnabled)
             {
                 plugins = plugins
-                    .Where(p => p.Configuration.EnableAutoUpdate)
+                    .Where(p => _config.CommonConfiguration.EnableAutoUpdate)
                     .ToList();
             }
 
             // Figure out what needs to be installed
             var packages = plugins.Select(p =>
             {
-                var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, p.Configuration.UpdateClass);
+                var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, _config.CommonConfiguration.SystemUpdateLevel);
 
                 return latestPluginInfo != null && latestPluginInfo.version != null && latestPluginInfo.version > p.Version ? latestPluginInfo : null;
 
@@ -367,7 +368,7 @@ namespace MediaBrowser.Common.Implementations.Updates
 
             var installationInfo = new InstallationInfo
             {
-                Id = Guid.NewGuid(),
+                Id = Guid.NewGuid().ToString("N"),
                 Name = package.name,
                 AssemblyGuid = package.guid,
                 UpdateClass = package.classification,
@@ -510,13 +511,14 @@ namespace MediaBrowser.Common.Implementations.Updates
             cancellationToken.ThrowIfCancellationRequested();
 
             // Validate with a checksum
-            if (package.checksum != Guid.Empty) // support for legacy uploads for now
+            var packageChecksum = string.IsNullOrWhiteSpace(package.checksum) ? Guid.Empty : new Guid(package.checksum);
+            if (packageChecksum != Guid.Empty) // support for legacy uploads for now
             {
                 using (var crypto = new MD5CryptoServiceProvider())
                 using (var stream = new BufferedStream(File.OpenRead(tempFile), 100000))
                 {
                     var check = Guid.Parse(BitConverter.ToString(crypto.ComputeHash(stream)).Replace("-", String.Empty));
-                    if (check != package.checksum)
+                    if (check != packageChecksum)
                     {
                         throw new ApplicationException(string.Format("Download validation failed for {0}.  Probably corrupted during transfer.", package.name));
                     }

+ 2 - 13
MediaBrowser.Common/IApplicationHost.cs

@@ -1,5 +1,5 @@
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Updates;
 using System;
 using System.Collections.Generic;
@@ -152,15 +152,4 @@ namespace MediaBrowser.Common
         /// <returns>System.Object.</returns>
         object CreateInstance(Type type);
     }
-
-    public interface IDependencyContainer
-    {
-        void RegisterSingleInstance<T>(T obj, bool manageLifetime = true)
-            where T : class;
-
-        void RegisterSingleInstance<T>(Func<T> func)
-            where T : class;
-
-        void Register(Type typeInterface, Type typeImplementation);
-    }
 }

+ 15 - 0
MediaBrowser.Common/IDependencyContainer.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace MediaBrowser.Common
+{
+    public interface IDependencyContainer
+    {
+        void RegisterSingleInstance<T>(T obj, bool manageLifetime = true)
+            where T : class;
+
+        void RegisterSingleInstance<T>(Func<T> func)
+            where T : class;
+
+        void Register(Type typeInterface, Type typeImplementation);
+    }
+}

+ 5 - 1
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -59,8 +59,8 @@
     <Compile Include="Constants\Constants.cs" />
     <Compile Include="Events\EventHelper.cs" />
     <Compile Include="Extensions\BaseExtensions.cs" />
-    <Compile Include="Events\GenericEventArgs.cs" />
     <Compile Include="Extensions\ResourceNotFoundException.cs" />
+    <Compile Include="IDependencyContainer.cs" />
     <Compile Include="IO\FileSystemRepository.cs" />
     <Compile Include="IO\IFileSystem.cs" />
     <Compile Include="IO\ProgressStream.cs" />
@@ -79,9 +79,12 @@
     <Compile Include="Net\IWebSocketServer.cs" />
     <Compile Include="Net\MimeTypes.cs" />
     <Compile Include="Net\WebSocketConnectEventArgs.cs" />
+    <Compile Include="Net\WebSocketMessageInfo.cs" />
     <Compile Include="Plugins\IDependencyModule.cs" />
     <Compile Include="Plugins\IPlugin.cs" />
     <Compile Include="Progress\ActionableProgress.cs" />
+    <Compile Include="ScheduledTasks\IConfigurableScheduledTask.cs" />
+    <Compile Include="ScheduledTasks\IHasKey.cs" />
     <Compile Include="ScheduledTasks\IScheduledTask.cs" />
     <Compile Include="ScheduledTasks\IScheduledTaskWorker.cs" />
     <Compile Include="ScheduledTasks\ITaskManager.cs" />
@@ -99,6 +102,7 @@
     <Compile Include="Security\ISecurityManager.cs" />
     <Compile Include="Updates\IInstallationManager.cs" />
     <Compile Include="Updates\InstallationEventArgs.cs" />
+    <Compile Include="Updates\InstallationFailedEventArgs.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="app.config" />

+ 84 - 14
MediaBrowser.Common/Net/BasePeriodicWebSocketListener.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.Common.Net
     /// <typeparam name="TReturnDataType">The type of the T return data type.</typeparam>
     /// <typeparam name="TStateType">The type of the T state type.</typeparam>
     public abstract class BasePeriodicWebSocketListener<TReturnDataType, TStateType> : IWebSocketListener, IDisposable
-        where TStateType : class, new()
+        where TStateType : WebSocketListenerState, new()
         where TReturnDataType : class
     {
         /// <summary>
@@ -83,7 +83,15 @@ namespace MediaBrowser.Common.Net
         }
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-        
+
+        protected virtual bool SendOnTimer
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         /// <summary>
         /// Starts sending messages over a web socket
         /// </summary>
@@ -99,9 +107,15 @@ namespace MediaBrowser.Common.Net
 
             Logger.Info("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name);
 
-            var timer = new Timer(TimerCallback, message.Connection, Timeout.Infinite, Timeout.Infinite);
+            var timer = SendOnTimer ?
+                new Timer(TimerCallback, message.Connection, Timeout.Infinite, Timeout.Infinite) :
+                null;
 
-            var state = new TStateType();
+            var state = new TStateType
+            {
+                IntervalMs = periodMs,
+                InitialDelayMs = dueTimeMs
+            };
 
             var semaphore = new SemaphoreSlim(1, 1);
 
@@ -110,14 +124,17 @@ namespace MediaBrowser.Common.Net
                 ActiveConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim>(message.Connection, cancellationTokenSource, timer, state, semaphore));
             }
 
-            timer.Change(TimeSpan.FromMilliseconds(dueTimeMs), TimeSpan.FromMilliseconds(periodMs));
+            if (timer != null)
+            {
+                timer.Change(TimeSpan.FromMilliseconds(dueTimeMs), TimeSpan.FromMilliseconds(periodMs));
+            }
         }
 
         /// <summary>
         /// Timers the callback.
         /// </summary>
         /// <param name="state">The state.</param>
-        private async void TimerCallback(object state)
+        private void TimerCallback(object state)
         {
             var connection = (IWebSocketConnection)state;
 
@@ -139,11 +156,50 @@ namespace MediaBrowser.Common.Net
                 return;
             }
 
+            SendData(tuple);
+        }
+
+        protected void SendData(bool force)
+        {
+            List<Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim>> tuples;
+
+            lock (ActiveConnections)
+            {
+                tuples = ActiveConnections
+                    .Where(c =>
+                    {
+                        if (c.Item1.State == WebSocketState.Open && !c.Item2.IsCancellationRequested)
+                        {
+                            var state = c.Item4;
+
+                            if (force || (DateTime.UtcNow - state.DateLastSendUtc).TotalMilliseconds >= state.IntervalMs)
+                            {
+                                return true;
+                            }
+                        }
+
+                        return false;
+                    })
+                    .ToList();
+            }
+
+            foreach (var tuple in tuples)
+            {
+                SendData(tuple);
+            }
+        }
+
+        private async void SendData(Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim> tuple)
+        {
+            var connection = tuple.Item1;
+
             try
             {
                 await tuple.Item5.WaitAsync(tuple.Item2.Token).ConfigureAwait(false);
 
-                var data = await GetDataToSend(tuple.Item4).ConfigureAwait(false);
+                var state = tuple.Item4;
+
+                var data = await GetDataToSend(state).ConfigureAwait(false);
 
                 if (data != null)
                 {
@@ -153,6 +209,8 @@ namespace MediaBrowser.Common.Net
                         Data = data
 
                     }, tuple.Item2.Token).ConfigureAwait(false);
+
+                    state.DateLastSendUtc = DateTime.UtcNow;
                 }
 
                 tuple.Item5.Release();
@@ -196,13 +254,18 @@ namespace MediaBrowser.Common.Net
         {
             Logger.Info("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name);
 
-            try
-            {
-                connection.Item3.Dispose();
-            }
-            catch (ObjectDisposedException)
+            var timer = connection.Item3;
+
+            if (timer != null)
             {
+                try
+                {
+                    timer.Dispose();
+                }
+                catch (ObjectDisposedException)
+                {
 
+                }
             }
 
             try
@@ -212,7 +275,7 @@ namespace MediaBrowser.Common.Net
             }
             catch (ObjectDisposedException)
             {
-                
+
             }
 
             try
@@ -223,7 +286,7 @@ namespace MediaBrowser.Common.Net
             {
 
             }
-            
+
             ActiveConnections.Remove(connection);
         }
 
@@ -253,4 +316,11 @@ namespace MediaBrowser.Common.Net
             Dispose(true);
         }
     }
+
+    public class WebSocketListenerState
+    {
+        public DateTime DateLastSendUtc { get; set; }
+        public long InitialDelayMs { get; set; }
+        public long IntervalMs { get; set; }
+    }
 }

+ 7 - 10
MediaBrowser.Common/Net/HttpRequestOptions.cs

@@ -52,6 +52,12 @@ namespace MediaBrowser.Common.Net
             }
         }
 
+        /// <summary>
+        /// Gets or sets the host.
+        /// </summary>
+        /// <value>The host.</value>
+        public string Host { get; set; }
+
         /// <summary>
         /// Gets or sets the progress.
         /// </summary>
@@ -69,14 +75,13 @@ namespace MediaBrowser.Common.Net
         public string RequestContentType { get; set; }
 
         public string RequestContent { get; set; }
+        public byte[] RequestContentBytes { get; set; }
 
         public bool BufferContent { get; set; }
 
         public bool LogRequest { get; set; }
 
         public bool LogErrorResponseBody { get; set; }
-        
-        public HttpRequestCachePolicy CachePolicy { get; set; }
 
         private string GetHeaderValue(string name)
         {
@@ -95,17 +100,9 @@ namespace MediaBrowser.Common.Net
             EnableHttpCompression = true;
             BufferContent = true;
 
-            CachePolicy = HttpRequestCachePolicy.None;
-
             RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
             LogRequest = true;
         }
     }
-
-    public enum HttpRequestCachePolicy
-    {
-        None = 1,
-        Validate = 2
-    }
 }

+ 0 - 12
MediaBrowser.Common/Net/IWebSocketConnection.cs

@@ -65,16 +65,4 @@ namespace MediaBrowser.Common.Net
         /// <exception cref="System.ArgumentNullException">buffer</exception>
         Task SendAsync(byte[] buffer, WebSocketMessageType type, CancellationToken cancellationToken);
     }
-
-    /// <summary>
-    /// Class WebSocketMessageInfo
-    /// </summary>
-    public class WebSocketMessageInfo : WebSocketMessage<string>
-    {
-        /// <summary>
-        /// Gets or sets the connection.
-        /// </summary>
-        /// <value>The connection.</value>
-        public IWebSocketConnection Connection { get; set; }
-    }
 }

+ 16 - 0
MediaBrowser.Common/Net/WebSocketMessageInfo.cs

@@ -0,0 +1,16 @@
+using MediaBrowser.Model.Net;
+
+namespace MediaBrowser.Common.Net
+{
+    /// <summary>
+    /// Class WebSocketMessageInfo
+    /// </summary>
+    public class WebSocketMessageInfo : WebSocketMessage<string>
+    {
+        /// <summary>
+        /// Gets or sets the connection.
+        /// </summary>
+        /// <value>The connection.</value>
+        public IWebSocketConnection Connection { get; set; }
+    }
+}

+ 0 - 2
MediaBrowser.Common/Plugins/BasePlugin.cs

@@ -305,8 +305,6 @@ namespace MediaBrowser.Common.Plugins
                 ConfigurationDateLastModified = ConfigurationDateLastModified,
                 Description = Description,
                 Id = Id.ToString("N"),
-                EnableAutoUpdate = Configuration.EnableAutoUpdate,
-                UpdateClass = Configuration.UpdateClass,
                 ConfigurationFileName = ConfigurationFileName
             };
 

+ 16 - 0
MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs

@@ -0,0 +1,16 @@
+namespace MediaBrowser.Common.ScheduledTasks
+{
+    public interface IConfigurableScheduledTask
+    {
+        /// <summary>
+        /// Gets a value indicating whether this instance is hidden.
+        /// </summary>
+        /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+        bool IsHidden { get; }
+        /// <summary>
+        /// Gets a value indicating whether this instance is enabled.
+        /// </summary>
+        /// <value><c>true</c> if this instance is enabled; otherwise, <c>false</c>.</value>
+        bool IsEnabled { get; }
+    }
+}

+ 7 - 0
MediaBrowser.Common/ScheduledTasks/IHasKey.cs

@@ -0,0 +1,7 @@
+namespace MediaBrowser.Common.ScheduledTasks
+{
+    public interface IHasKey
+    {
+        string Key { get; }
+    }
+}

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

@@ -42,23 +42,4 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         IEnumerable<ITaskTrigger> GetDefaultTriggers();
     }
-
-    public interface IConfigurableScheduledTask
-    {
-        /// <summary>
-        /// Gets a value indicating whether this instance is hidden.
-        /// </summary>
-        /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
-        bool IsHidden { get; }
-        /// <summary>
-        /// Gets a value indicating whether this instance is enabled.
-        /// </summary>
-        /// <value><c>true</c> if this instance is enabled; otherwise, <c>false</c>.</value>
-        bool IsEnabled { get; }
-    }
-
-    public interface IHasKey
-    {
-        string Key { get; }
-    }
 }

+ 8 - 2
MediaBrowser.Common/ScheduledTasks/IScheduledTaskWorker.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Tasks;
 using System;
 using System.Collections.Generic;
 
@@ -9,6 +10,11 @@ namespace MediaBrowser.Common.ScheduledTasks
     /// </summary>
     public interface IScheduledTaskWorker : IDisposable
     {
+        /// <summary>
+        /// Occurs when [task progress].
+        /// </summary>
+        event EventHandler<GenericEventArgs<double>> TaskProgress;
+
         /// <summary>
         /// Gets or sets the scheduled task.
         /// </summary>
@@ -62,6 +68,6 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// Gets the unique id.
         /// </summary>
         /// <value>The unique id.</value>
-        Guid Id { get; }
+        string Id { get; }
     }
 }

+ 3 - 4
MediaBrowser.Common/ScheduledTasks/ITaskManager.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Events;
-using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Events;
 using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
@@ -50,7 +49,7 @@ namespace MediaBrowser.Common.ScheduledTasks
         void Cancel(IScheduledTaskWorker task);
         Task Execute(IScheduledTaskWorker task);
 
-        event EventHandler<EventArgs> TaskExecuting;
-        event EventHandler<GenericEventArgs<TaskResult>> TaskCompleted;
+        event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting;
+        event EventHandler<TaskCompletionEventArgs> TaskCompleted;
     }
 }

+ 1 - 1
MediaBrowser.Common/ScheduledTasks/TaskCompletionEventArgs.cs

@@ -5,7 +5,7 @@ namespace MediaBrowser.Common.ScheduledTasks
 {
     public class TaskCompletionEventArgs : EventArgs
     {
-        public IScheduledTask Task { get; set; }
+        public IScheduledTaskWorker Task { get; set; }
 
         public TaskResult Result { get; set; }
     }

+ 2 - 2
MediaBrowser.Common/Updates/IInstallationManager.cs

@@ -1,5 +1,5 @@
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Updates;
 using System;
 using System.Collections.Concurrent;

+ 0 - 6
MediaBrowser.Common/Updates/InstallationEventArgs.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Model.Updates;
-using System;
 
 namespace MediaBrowser.Common.Updates
 {
@@ -9,9 +8,4 @@ namespace MediaBrowser.Common.Updates
 
         public PackageVersionInfo PackageVersionInfo { get; set; }
     }
-
-    public class InstallationFailedEventArgs : InstallationEventArgs
-    {
-        public Exception Exception { get; set; }
-    }
 }

+ 9 - 0
MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace MediaBrowser.Common.Updates
+{
+    public class InstallationFailedEventArgs : InstallationEventArgs
+    {
+        public Exception Exception { get; set; }
+    }
+}

+ 2 - 0
MediaBrowser.Controller/Channels/ChannelAudioItem.cs

@@ -8,6 +8,8 @@ namespace MediaBrowser.Controller.Channels
     {
         public string ExternalId { get; set; }
 
+        public string ChannelId { get; set; }
+        
         public ChannelItemType ChannelItemType { get; set; }
 
         public bool IsInfiniteStream { get; set; }

+ 2 - 0
MediaBrowser.Controller/Channels/ChannelCategoryItem.cs

@@ -7,6 +7,8 @@ namespace MediaBrowser.Controller.Channels
     {
         public string ExternalId { get; set; }
 
+        public string ChannelId { get; set; }
+        
         public ChannelItemType ChannelItemType { get; set; }
 
         public string OriginalImageUrl { get; set; }

+ 30 - 0
MediaBrowser.Controller/Channels/ChannelInfo.cs

@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Channels
+{
+    public class ChannelInfo
+    {
+        /// <summary>
+        /// Gets the home page URL.
+        /// </summary>
+        /// <value>The home page URL.</value>
+        public string HomePageUrl { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance can search.
+        /// </summary>
+        /// <value><c>true</c> if this instance can search; otherwise, <c>false</c>.</value>
+        public bool CanSearch { get; set; }
+
+        public List<ChannelMediaType> MediaTypes { get; set; }
+
+        public List<ChannelMediaContentType> ContentTypes { get; set; }
+
+        public ChannelInfo()
+        {
+            MediaTypes = new List<ChannelMediaType>();
+            ContentTypes = new List<ChannelMediaContentType>();
+        }
+    }
+
+}

+ 0 - 41
MediaBrowser.Controller/Channels/ChannelItemInfo.cs

@@ -52,45 +52,4 @@ namespace MediaBrowser.Controller.Channels
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
     }
-
-    public enum ChannelItemType
-    {
-        Media = 0,
-
-        Category = 1
-    }
-
-    public enum ChannelMediaType
-    {
-        Audio = 0,
-
-        Video = 1
-    }
-
-    public enum ChannelMediaContentType
-    {
-        Clip = 0,
-
-        Podcast = 1,
-
-        Trailer = 2,
-
-        Movie = 3,
-
-        Episode = 4,
-
-        Song = 5
-    }
-
-    public class ChannelMediaInfo
-    {
-        public string Path { get; set; }
-
-        public Dictionary<string, string> RequiredHttpHeaders { get; set; }
-
-        public ChannelMediaInfo()
-        {
-            RequiredHttpHeaders = new Dictionary<string, string>();
-        }
-    }
 }

+ 12 - 0
MediaBrowser.Controller/Channels/ChannelItemResult.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Channels
+{
+    public class ChannelItemResult
+    {
+        public List<ChannelItemInfo> Items { get; set; }
+
+        public TimeSpan CacheLength { get; set; }
+    }
+}

+ 9 - 0
MediaBrowser.Controller/Channels/ChannelItemType.cs

@@ -0,0 +1,9 @@
+namespace MediaBrowser.Controller.Channels
+{
+    public enum ChannelItemType
+    {
+        Media = 0,
+
+        Category = 1
+    }
+}

+ 17 - 0
MediaBrowser.Controller/Channels/ChannelMediaContentType.cs

@@ -0,0 +1,17 @@
+namespace MediaBrowser.Controller.Channels
+{
+    public enum ChannelMediaContentType
+    {
+        Clip = 0,
+
+        Podcast = 1,
+
+        Trailer = 2,
+
+        Movie = 3,
+
+        Episode = 4,
+
+        Song = 5
+    }
+}

+ 26 - 0
MediaBrowser.Controller/Channels/ChannelMediaInfo.cs

@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Channels
+{
+    public class ChannelMediaInfo
+    {
+        public string Path { get; set; }
+
+        public Dictionary<string, string> RequiredHttpHeaders { get; set; }
+
+        public string Container { get; set; }
+        public string AudioCodec { get; set; }
+        public string VideoCodec { get; set; }
+
+        public int? AudioBitrate { get; set; }
+        public int? VideoBitrate { get; set; }
+        public int? Width { get; set; }
+        public int? Height { get; set; }
+        public int? AudioChannels { get; set; }
+
+        public ChannelMediaInfo()
+        {
+            RequiredHttpHeaders = new Dictionary<string, string>();
+        }
+    }
+}

+ 9 - 0
MediaBrowser.Controller/Channels/ChannelMediaType.cs

@@ -0,0 +1,9 @@
+namespace MediaBrowser.Controller.Channels
+{
+    public enum ChannelMediaType
+    {
+        Audio = 0,
+
+        Video = 1
+    }
+}

+ 7 - 0
MediaBrowser.Controller/Channels/ChannelSearchInfo.cs

@@ -0,0 +1,7 @@
+namespace MediaBrowser.Controller.Channels
+{
+    public class ChannelSearchInfo
+    {
+        public string SearchTerm { get; set; }
+    }
+}

+ 2 - 0
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -10,6 +10,8 @@ namespace MediaBrowser.Controller.Channels
     {
         public string ExternalId { get; set; }
 
+        public string ChannelId { get; set; }
+        
         public ChannelItemType ChannelItemType { get; set; }
 
         public bool IsInfiniteStream { get; set; }

+ 0 - 45
MediaBrowser.Controller/Channels/IChannel.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using System;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
@@ -60,48 +59,4 @@ namespace MediaBrowser.Controller.Channels
         /// <returns>IEnumerable{ImageType}.</returns>
         IEnumerable<ImageType> GetSupportedChannelImages();
     }
-
-    public class ChannelInfo
-    {
-        /// <summary>
-        /// Gets the home page URL.
-        /// </summary>
-        /// <value>The home page URL.</value>
-        public string HomePageUrl { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance can search.
-        /// </summary>
-        /// <value><c>true</c> if this instance can search; otherwise, <c>false</c>.</value>
-        public bool CanSearch { get; set; }
-
-        public List<ChannelMediaType> MediaTypes { get; set; }
-
-        public List<ChannelMediaContentType> ContentTypes { get; set; }
-
-        public ChannelInfo()
-        {
-            MediaTypes = new List<ChannelMediaType>();
-            ContentTypes = new List<ChannelMediaContentType>();
-        }
-    }
-
-    public class ChannelSearchInfo
-    {
-        public string SearchTerm { get; set; }
-    }
-
-    public class InternalChannelItemQuery
-    {
-        public string CategoryId { get; set; }
-
-        public User User { get; set; }
-    }
-
-    public class ChannelItemResult
-    {
-        public List<ChannelItemInfo> Items { get; set; }
-
-        public TimeSpan CacheLength { get; set; }
-    }
 }

+ 9 - 0
MediaBrowser.Controller/Channels/IChannelFactory.cs

@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Channels
+{
+    public interface IChannelFactory
+    {
+        IEnumerable<IChannel> GetChannels();
+    }
+}

+ 2 - 7
MediaBrowser.Controller/Channels/IChannelItem.cs

@@ -4,17 +4,12 @@ namespace MediaBrowser.Controller.Channels
 {
     public interface IChannelItem : IHasImages
     {
+        string ChannelId { get; set; }
+
         string ExternalId { get; set; }
 
         ChannelItemType ChannelItemType { get; set; }
 
         string OriginalImageUrl { get; set; }
     }
-
-    public interface IChannelMediaItem : IChannelItem
-    {
-        bool IsInfiniteStream { get; set; }
-
-        ChannelMediaContentType ContentType { get; set; }
-    }
 }

+ 2 - 1
MediaBrowser.Controller/Channels/IChannelManager.cs

@@ -13,7 +13,8 @@ namespace MediaBrowser.Controller.Channels
         /// Adds the parts.
         /// </summary>
         /// <param name="channels">The channels.</param>
-        void AddParts(IEnumerable<IChannel> channels);
+        /// <param name="factories">The factories.</param>
+        void AddParts(IEnumerable<IChannel> channels, IEnumerable<IChannelFactory> factories);
 
         /// <summary>
         /// Gets the channels.

+ 9 - 0
MediaBrowser.Controller/Channels/IChannelMediaItem.cs

@@ -0,0 +1,9 @@
+namespace MediaBrowser.Controller.Channels
+{
+    public interface IChannelMediaItem : IChannelItem
+    {
+        bool IsInfiniteStream { get; set; }
+
+        ChannelMediaContentType ContentType { get; set; }
+    }
+}

+ 11 - 0
MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs

@@ -0,0 +1,11 @@
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Channels
+{
+    public class InternalChannelItemQuery
+    {
+        public string CategoryId { get; set; }
+
+        public User User { get; set; }
+    }
+}

+ 19 - 0
MediaBrowser.Controller/Chapters/ChapterResponse.cs

@@ -0,0 +1,19 @@
+using MediaBrowser.Model.Chapters;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Chapters
+{
+    public class ChapterResponse
+    {
+        /// <summary>
+        /// Gets or sets the chapters.
+        /// </summary>
+        /// <value>The chapters.</value>
+        public List<RemoteChapterInfo> Chapters { get; set; }
+
+        public ChapterResponse()
+        {
+            Chapters = new List<RemoteChapterInfo>();
+        }
+    }
+}

+ 29 - 0
MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs

@@ -0,0 +1,29 @@
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Chapters
+{
+    public class ChapterSearchRequest : IHasProviderIds
+    {
+        public string Language { get; set; }
+
+        public VideoContentType ContentType { get; set; }
+
+        public string MediaPath { get; set; }
+        public string SeriesName { get; set; }
+        public string Name { get; set; }
+        public int? IndexNumber { get; set; }
+        public int? IndexNumberEnd { get; set; }
+        public int? ParentIndexNumber { get; set; }
+        public int? ProductionYear { get; set; }
+        public long? RuntimeTicks { get; set; }
+        public Dictionary<string, string> ProviderIds { get; set; }
+
+        public ChapterSearchRequest()
+        {
+            ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+        }
+    }
+}

+ 39 - 0
MediaBrowser.Controller/Chapters/IChapterProvider.cs

@@ -0,0 +1,39 @@
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Chapters;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Chapters
+{
+    public interface IChapterProvider
+    {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+
+        /// <summary>
+        /// Gets the supported media types.
+        /// </summary>
+        /// <value>The supported media types.</value>
+        IEnumerable<VideoContentType> SupportedMediaTypes { get; }
+
+        /// <summary>
+        /// Searches the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{RemoteChapterResult}}.</returns>
+        Task<IEnumerable<RemoteChapterResult>> Search(ChapterSearchRequest request, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the chapters.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{ChapterResponse}.</returns>
+        Task<ChapterResponse> GetChapters(string id, CancellationToken cancellationToken);
+    }
+}

+ 7 - 0
MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs

@@ -1,5 +1,7 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Events;
+using System;
 
 namespace MediaBrowser.Controller.Configuration
 {
@@ -8,6 +10,11 @@ namespace MediaBrowser.Controller.Configuration
     /// </summary>
     public interface IServerConfigurationManager : IConfigurationManager
     {
+        /// <summary>
+        /// Occurs when [configuration updating].
+        /// </summary>
+        event EventHandler<GenericEventArgs<ServerConfiguration>> ConfigurationUpdating;
+        
         /// <summary>
         /// Gets the application paths.
         /// </summary>

+ 0 - 14
MediaBrowser.Controller/Dlna/ControlRequest.cs

@@ -17,18 +17,4 @@ namespace MediaBrowser.Controller.Dlna
             Headers = new Dictionary<string, string>();
         }
     }
-
-    public class ControlResponse
-    {
-        public IDictionary<string, string> Headers { get; set; }
-
-        public string Xml { get; set; }
-
-        public bool IsSuccessful { get; set; }
-
-        public ControlResponse()
-        {
-            Headers = new Dictionary<string, string>();
-        }
-    }
 }

+ 18 - 0
MediaBrowser.Controller/Dlna/ControlResponse.cs

@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Dlna
+{
+    public class ControlResponse
+    {
+        public IDictionary<string, string> Headers { get; set; }
+
+        public string Xml { get; set; }
+
+        public bool IsSuccessful { get; set; }
+
+        public ControlResponse()
+        {
+            Headers = new Dictionary<string, string>();
+        }
+    }
+}

+ 2 - 22
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -55,7 +55,7 @@ namespace MediaBrowser.Controller.Drawing
         /// <param name="item">The item.</param>
         /// <param name="image">The image.</param>
         /// <returns>Guid.</returns>
-        Guid GetImageCacheTag(IHasImages item, ItemImageInfo image);
+        string GetImageCacheTag(IHasImages item, ItemImageInfo image);
 
         /// <summary>
         /// Gets the image cache tag.
@@ -66,7 +66,7 @@ namespace MediaBrowser.Controller.Drawing
         /// <param name="dateModified">The date modified.</param>
         /// <param name="imageEnhancers">The image enhancers.</param>
         /// <returns>Guid.</returns>
-        Guid GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified,
+        string GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified,
                               List<IImageEnhancer> imageEnhancers);
 
         /// <summary>
@@ -86,24 +86,4 @@ namespace MediaBrowser.Controller.Drawing
         /// <returns>Task{System.String}.</returns>
         Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex);
     }
-
-    public static class ImageProcessorExtensions
-    {
-        public static Guid? GetImageCacheTag(this IImageProcessor processor, IHasImages item, ImageType imageType)
-        {
-            return processor.GetImageCacheTag(item, imageType, 0);
-        }
-        
-        public static Guid? GetImageCacheTag(this IImageProcessor processor, IHasImages item, ImageType imageType, int imageIndex)
-        {
-            var imageInfo = item.GetImageInfo(imageType, imageIndex);
-
-            if (imageInfo == null)
-            {
-                return null;
-            }
-
-            return processor.GetImageCacheTag(item, imageInfo);
-        }
-    }
 }

+ 25 - 0
MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs

@@ -0,0 +1,25 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Drawing
+{
+    public static class ImageProcessorExtensions
+    {
+        public static string GetImageCacheTag(this IImageProcessor processor, IHasImages item, ImageType imageType)
+        {
+            return processor.GetImageCacheTag(item, imageType, 0);
+        }
+        
+        public static string GetImageCacheTag(this IImageProcessor processor, IHasImages item, ImageType imageType, int imageIndex)
+        {
+            var imageInfo = item.GetImageInfo(imageType, imageIndex);
+
+            if (imageInfo == null)
+            {
+                return null;
+            }
+
+            return processor.GetImageCacheTag(item, imageInfo);
+        }
+    }
+}

+ 1 - 7
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -305,13 +305,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
                 if (!ParentIndexNumber.HasValue && !string.IsNullOrEmpty(Path))
                 {
-                    ParentIndexNumber = TVUtils.GetSeasonNumberFromPath(Path);
-
-                    // If a change was made record it
-                    if (ParentIndexNumber.HasValue)
-                    {
-                        hasChanges = true;
-                    }
+                    ParentIndexNumber = TVUtils.GetSeasonNumberFromEpisodeFile(Path);
                 }
 
                 // If a change was made record it

+ 0 - 16
MediaBrowser.Controller/Library/IIntroProvider.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Controller.Entities;
-using System;
 using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Library
@@ -23,19 +22,4 @@ namespace MediaBrowser.Controller.Library
         /// <returns>IEnumerable{System.String}.</returns>
         IEnumerable<string> GetAllIntroFiles();
     }
-
-    public class IntroInfo
-    {
-        /// <summary>
-        /// Gets or sets the path.
-        /// </summary>
-        /// <value>The path.</value>
-        public string Path { get; set; }
-
-        /// <summary>
-        /// Gets or sets the item id.
-        /// </summary>
-        /// <value>The item id.</value>
-        public Guid? ItemId { get; set; }
-    }
 }

+ 0 - 16
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -333,20 +333,4 @@ namespace MediaBrowser.Controller.Library
         /// <returns>IEnumerable{BaseItem}.</returns>
         IEnumerable<BaseItem> ReplaceVideosWithPrimaryVersions(IEnumerable<BaseItem> items);
     }
-
-    public static class LibraryManagerExtensions
-    {
-        public static Task DeleteItem(this ILibraryManager manager, BaseItem item)
-        {
-            return manager.DeleteItem(item, new DeleteOptions
-            {
-                DeleteFileLocation = true
-            });
-        }
-
-        public static BaseItem GetItemById(this ILibraryManager manager, string id)
-        {
-            return manager.GetItemById(new Guid(id));
-        }
-    }
 }

+ 14 - 0
MediaBrowser.Controller/Library/IMetadataFileSaver.cs

@@ -0,0 +1,14 @@
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Library
+{
+    public interface IMetadataFileSaver : IMetadataSaver
+    {
+        /// <summary>
+        /// Gets the save path.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>System.String.</returns>
+        string GetSavePath(IHasMetadata item);
+    }
+}

+ 0 - 10
MediaBrowser.Controller/Library/IMetadataSaver.cs

@@ -31,14 +31,4 @@ namespace MediaBrowser.Controller.Library
         /// <returns>Task.</returns>
         void Save(IHasMetadata item, CancellationToken cancellationToken);
     }
-
-    public interface IMetadataFileSaver : IMetadataSaver
-    {
-        /// <summary>
-        /// Gets the save path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        string GetSavePath(IHasMetadata item);
-    }
 }

+ 2 - 2
MediaBrowser.Controller/Library/IUserManager.cs

@@ -1,5 +1,5 @@
-using MediaBrowser.Common.Events;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Events;
 using System;
 using System.Collections.Generic;
 using System.Threading;

+ 19 - 0
MediaBrowser.Controller/Library/IntroInfo.cs

@@ -0,0 +1,19 @@
+using System;
+
+namespace MediaBrowser.Controller.Library
+{
+    public class IntroInfo
+    {
+        /// <summary>
+        /// Gets or sets the path.
+        /// </summary>
+        /// <value>The path.</value>
+        public string Path { get; set; }
+
+        /// <summary>
+        /// Gets or sets the item id.
+        /// </summary>
+        /// <value>The item id.</value>
+        public Guid? ItemId { get; set; }
+    }
+}

+ 22 - 0
MediaBrowser.Controller/Library/LibraryManagerExtensions.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Library
+{
+    public static class LibraryManagerExtensions
+    {
+        public static Task DeleteItem(this ILibraryManager manager, BaseItem item)
+        {
+            return manager.DeleteItem(item, new DeleteOptions
+            {
+                DeleteFileLocation = true
+            });
+        }
+
+        public static BaseItem GetItemById(this ILibraryManager manager, string id)
+        {
+            return manager.GetItemById(new Guid(id));
+        }
+    }
+}

+ 0 - 9
MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs

@@ -24,13 +24,4 @@ namespace MediaBrowser.Controller.Library
             Users = new List<User>();
         }
     }
-
-    public class PlaybackStopEventArgs : PlaybackProgressEventArgs
-    {
-        /// <summary>
-        /// Gets or sets a value indicating whether [played to completion].
-        /// </summary>
-        /// <value><c>true</c> if [played to completion]; otherwise, <c>false</c>.</value>
-        public bool PlayedToCompletion { get; set; }
-    }
 }

+ 11 - 0
MediaBrowser.Controller/Library/PlaybackStopEventArgs.cs

@@ -0,0 +1,11 @@
+namespace MediaBrowser.Controller.Library
+{
+    public class PlaybackStopEventArgs : PlaybackProgressEventArgs
+    {
+        /// <summary>
+        /// Gets or sets a value indicating whether [played to completion].
+        /// </summary>
+        /// <value><c>true</c> if [played to completion]; otherwise, <c>false</c>.</value>
+        public bool PlayedToCompletion { get; set; }
+    }
+}

+ 9 - 0
MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs

@@ -0,0 +1,9 @@
+namespace MediaBrowser.Controller.LiveTv
+{
+    /// <summary>
+    /// Class LiveTvConflictException.
+    /// </summary>
+    public class LiveTvConflictException : LiveTvException
+    {
+    }
+}

+ 0 - 7
MediaBrowser.Controller/LiveTv/LiveTvException.cs

@@ -8,11 +8,4 @@ namespace MediaBrowser.Controller.LiveTv
     public class LiveTvException : Exception
     {
     }
-
-    /// <summary>
-    /// Class LiveTvConflictException.
-    /// </summary>
-    public class LiveTvConflictException : LiveTvException
-    {
-    }
 }

+ 0 - 56
MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs

@@ -40,60 +40,4 @@ namespace MediaBrowser.Controller.LiveTv
             Tuners = new List<LiveTvTunerInfo>();
         }
     }
-
-    public class LiveTvTunerInfo
-    {
-        /// <summary>
-        /// Gets or sets the type of the source.
-        /// </summary>
-        /// <value>The type of the source.</value>
-        public string SourceType { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        public string Name { get; set; }
-
-        /// <summary>
-        /// Gets or sets the identifier.
-        /// </summary>
-        /// <value>The identifier.</value>
-        public string Id { get; set; }
-
-        /// <summary>
-        /// Gets or sets the status.
-        /// </summary>
-        /// <value>The status.</value>
-        public LiveTvTunerStatus Status { get; set; }
-
-        /// <summary>
-        /// Gets or sets the channel identifier.
-        /// </summary>
-        /// <value>The channel identifier.</value>
-        public string ChannelId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the recording identifier.
-        /// </summary>
-        /// <value>The recording identifier.</value>
-        public string RecordingId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name of the program.
-        /// </summary>
-        /// <value>The name of the program.</value>
-        public string ProgramName { get; set; }
-        
-        /// <summary>
-        /// Gets or sets the clients.
-        /// </summary>
-        /// <value>The clients.</value>
-        public List<string> Clients { get; set; }
-
-        public LiveTvTunerInfo()
-        {
-            Clients = new List<string>();
-        }
-    }
 }

+ 61 - 0
MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs

@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+    public class LiveTvTunerInfo
+    {
+        /// <summary>
+        /// Gets or sets the type of the source.
+        /// </summary>
+        /// <value>The type of the source.</value>
+        public string SourceType { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the identifier.
+        /// </summary>
+        /// <value>The identifier.</value>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets the status.
+        /// </summary>
+        /// <value>The status.</value>
+        public LiveTvTunerStatus Status { get; set; }
+
+        /// <summary>
+        /// Gets or sets the channel identifier.
+        /// </summary>
+        /// <value>The channel identifier.</value>
+        public string ChannelId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the recording identifier.
+        /// </summary>
+        /// <value>The recording identifier.</value>
+        public string RecordingId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the program.
+        /// </summary>
+        /// <value>The name of the program.</value>
+        public string ProgramName { get; set; }
+        
+        /// <summary>
+        /// Gets or sets the clients.
+        /// </summary>
+        /// <value>The clients.</value>
+        public List<string> Clients { get; set; }
+
+        public LiveTvTunerInfo()
+        {
+            Clients = new List<string>();
+        }
+    }
+}

+ 0 - 0
MediaBrowser.Controller/LiveTv/EventArgs.cs → MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs


+ 28 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -69,16 +69,30 @@
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
     <Compile Include="Channels\ChannelCategoryItem.cs" />
+    <Compile Include="Channels\ChannelInfo.cs" />
     <Compile Include="Channels\ChannelItemInfo.cs" />
+    <Compile Include="Channels\ChannelItemResult.cs" />
+    <Compile Include="Channels\ChannelItemType.cs" />
+    <Compile Include="Channels\ChannelMediaContentType.cs" />
+    <Compile Include="Channels\ChannelMediaInfo.cs" />
+    <Compile Include="Channels\ChannelMediaType.cs" />
+    <Compile Include="Channels\ChannelSearchInfo.cs" />
     <Compile Include="Channels\IChannel.cs" />
+    <Compile Include="Channels\IChannelFactory.cs" />
     <Compile Include="Channels\IChannelManager.cs" />
     <Compile Include="Channels\IChannelItem.cs" />
     <Compile Include="Channels\ChannelAudioItem.cs" />
     <Compile Include="Channels\ChannelVideoItem.cs" />
     <Compile Include="Channels\Channel.cs" />
+    <Compile Include="Channels\IChannelMediaItem.cs" />
+    <Compile Include="Channels\InternalChannelItemQuery.cs" />
+    <Compile Include="Chapters\ChapterSearchRequest.cs" />
+    <Compile Include="Chapters\IChapterProvider.cs" />
+    <Compile Include="Chapters\ChapterResponse.cs" />
     <Compile Include="Collections\CollectionCreationOptions.cs" />
     <Compile Include="Collections\ICollectionManager.cs" />
     <Compile Include="Dlna\ControlRequest.cs" />
+    <Compile Include="Dlna\ControlResponse.cs" />
     <Compile Include="Dlna\DlnaIconResponse.cs" />
     <Compile Include="Dlna\EventSubscriptionResponse.cs" />
     <Compile Include="Dlna\IContentDirectory.cs" />
@@ -87,6 +101,7 @@
     <Compile Include="Drawing\IImageProcessor.cs" />
     <Compile Include="Drawing\ImageFormat.cs" />
     <Compile Include="Drawing\ImageProcessingOptions.cs" />
+    <Compile Include="Drawing\ImageProcessorExtensions.cs" />
     <Compile Include="Dto\IDtoService.cs" />
     <Compile Include="Entities\AdultVideo.cs" />
     <Compile Include="Entities\Audio\IHasAlbumArtist.cs" />
@@ -131,12 +146,16 @@
     <Compile Include="FileOrganization\IFileOrganizationService.cs" />
     <Compile Include="Library\DeleteOptions.cs" />
     <Compile Include="Library\ILibraryPostScanTask.cs" />
+    <Compile Include="Library\IMetadataFileSaver.cs" />
     <Compile Include="Library\IMetadataSaver.cs" />
     <Compile Include="Library\IMusicManager.cs" />
+    <Compile Include="Library\IntroInfo.cs" />
     <Compile Include="Library\ItemUpdateType.cs" />
     <Compile Include="Library\IUserDataManager.cs" />
+    <Compile Include="Library\LibraryManagerExtensions.cs" />
+    <Compile Include="Library\PlaybackStopEventArgs.cs" />
     <Compile Include="Library\UserDataSaveEventArgs.cs" />
-    <Compile Include="LiveTv\EventArgs.cs" />
+    <Compile Include="LiveTv\RecordingStatusChangedEventArgs.cs" />
     <Compile Include="LiveTv\ILiveTvRecording.cs" />
     <Compile Include="LiveTv\LiveStreamInfo.cs" />
     <Compile Include="LiveTv\LiveTvAudioRecording.cs" />
@@ -144,8 +163,10 @@
     <Compile Include="LiveTv\ChannelInfo.cs" />
     <Compile Include="LiveTv\ILiveTvManager.cs" />
     <Compile Include="LiveTv\ILiveTvService.cs" />
+    <Compile Include="LiveTv\LiveTvConflictException.cs" />
     <Compile Include="LiveTv\LiveTvException.cs" />
     <Compile Include="LiveTv\LiveTvServiceStatusInfo.cs" />
+    <Compile Include="LiveTv\LiveTvTunerInfo.cs" />
     <Compile Include="LiveTv\StreamResponseInfo.cs" />
     <Compile Include="LiveTv\LiveTvProgram.cs" />
     <Compile Include="LiveTv\LiveTvVideoRecording.cs" />
@@ -190,6 +211,10 @@
     <Compile Include="Providers\IMetadataProvider.cs" />
     <Compile Include="Providers\IMetadataService.cs" />
     <Compile Include="Providers\IRemoteMetadataProvider.cs" />
+    <Compile Include="Providers\VideoContentType.cs" />
+    <Compile Include="Security\IEncryptionManager.cs" />
+    <Compile Include="Subtitles\ISubtitleManager.cs" />
+    <Compile Include="Subtitles\ISubtitleProvider.cs" />
     <Compile Include="Providers\ItemLookupInfo.cs" />
     <Compile Include="Providers\MetadataRefreshOptions.cs" />
     <Compile Include="Providers\NameParser.cs" />
@@ -265,6 +290,8 @@
     <Compile Include="Sorting\IUserBaseItemComparer.cs" />
     <Compile Include="Providers\BaseItemXmlParser.cs" />
     <Compile Include="Sorting\SortExtensions.cs" />
+    <Compile Include="Subtitles\SubtitleResponse.cs" />
+    <Compile Include="Subtitles\SubtitleSearchRequest.cs" />
     <Compile Include="Themes\IAppThemeManager.cs" />
     <Compile Include="Themes\InternalThemeImage.cs" />
   </ItemGroup>

+ 3 - 1
MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs

@@ -187,7 +187,9 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
             else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
             {
-                stream.Type = MediaStreamType.Video;
+                stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1
+                    ? MediaStreamType.EmbeddedImage
+                    : MediaStreamType.Video;
 
                 stream.Width = streamInfo.width;
                 stream.Height = streamInfo.height;

+ 1 - 1
MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs

@@ -34,6 +34,6 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="userId">The user id.</param>
         /// <param name="client">The client.</param>
         /// <returns>Task{DisplayPreferences}.</returns>
-        DisplayPreferences GetDisplayPreferences(Guid displayPreferencesId, Guid userId, string client);
+        DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client);
     }
 }

+ 19 - 1
MediaBrowser.Controller/Providers/DirectoryService.cs

@@ -11,6 +11,7 @@ namespace MediaBrowser.Controller.Providers
     {
         List<FileSystemInfo> GetFileSystemEntries(string path);
         IEnumerable<FileSystemInfo> GetFiles(string path);
+        IEnumerable<FileSystemInfo> GetFiles(string path, bool clearCache);
         FileSystemInfo GetFile(string path);
     }
 
@@ -26,9 +27,21 @@ namespace MediaBrowser.Controller.Providers
         }
 
         public List<FileSystemInfo> GetFileSystemEntries(string path)
+        {
+            return GetFileSystemEntries(path, false);
+        }
+
+        private List<FileSystemInfo> GetFileSystemEntries(string path, bool clearCache)
         {
             List<FileSystemInfo> entries;
 
+            if (clearCache)
+            {
+                List<FileSystemInfo> removed;
+
+                _cache.TryRemove(path, out removed);
+            }
+
             if (!_cache.TryGetValue(path, out entries))
             {
                 //_logger.Debug("Getting files for " + path);
@@ -50,7 +63,12 @@ namespace MediaBrowser.Controller.Providers
 
         public IEnumerable<FileSystemInfo> GetFiles(string path)
         {
-            return GetFileSystemEntries(path).Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory);
+            return GetFiles(path, false);
+        }
+
+        public IEnumerable<FileSystemInfo> GetFiles(string path, bool clearCache)
+        {
+            return GetFileSystemEntries(path, clearCache).Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory);
         }
 
         public FileSystemInfo GetFile(string path)

+ 19 - 0
MediaBrowser.Controller/Providers/VideoContentType.cs

@@ -0,0 +1,19 @@
+
+namespace MediaBrowser.Controller.Providers
+{
+    /// <summary>
+    /// Enum VideoContentType
+    /// </summary>
+    public enum VideoContentType
+    {
+        /// <summary>
+        /// The episode
+        /// </summary>
+        Episode = 0,
+
+        /// <summary>
+        /// The movie
+        /// </summary>
+        Movie = 1
+    }
+}

+ 20 - 0
MediaBrowser.Controller/Security/IEncryptionManager.cs

@@ -0,0 +1,20 @@
+
+namespace MediaBrowser.Controller.Security
+{
+    public interface IEncryptionManager
+    {
+        /// <summary>
+        /// Encrypts the string.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns>System.String.</returns>
+        string EncryptString(string value);
+
+        /// <summary>
+        /// Decrypts the string.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns>System.String.</returns>
+        string DecryptString(string value);
+    }
+}

+ 0 - 8
MediaBrowser.Controller/Session/ISessionController.cs

@@ -19,14 +19,6 @@ namespace MediaBrowser.Controller.Session
         /// <value><c>true</c> if this instance is session active; otherwise, <c>false</c>.</value>
         bool IsSessionActive { get; }
 
-        /// <summary>
-        /// Sends the message command.
-        /// </summary>
-        /// <param name="command">The command.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken);
-
         /// <summary>
         /// Sends the play command.
         /// </summary>

+ 50 - 0
MediaBrowser.Controller/Subtitles/ISubtitleManager.cs

@@ -0,0 +1,50 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Providers;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Subtitles
+{
+    public interface ISubtitleManager
+    {
+        /// <summary>
+        /// Adds the parts.
+        /// </summary>
+        /// <param name="subtitleProviders">The subtitle providers.</param>
+        void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders);
+
+        /// <summary>
+        /// Searches the subtitles.
+        /// </summary>
+        /// <param name="video">The video.</param>
+        /// <param name="language">The language.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns>
+        Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(Video video,
+            string language,
+            CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Searches the subtitles.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns>
+        Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, 
+            CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Downloads the subtitles.
+        /// </summary>
+        /// <param name="video">The video.</param>
+        /// <param name="subtitleId">The subtitle identifier.</param>
+        /// <param name="providerName">Name of the provider.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task DownloadSubtitles(Video video, 
+            string subtitleId, 
+            string providerName, 
+            CancellationToken cancellationToken);
+    }
+}

+ 39 - 0
MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs

@@ -0,0 +1,39 @@
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Providers;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Subtitles
+{
+    public interface ISubtitleProvider
+    {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+
+        /// <summary>
+        /// Gets the supported media types.
+        /// </summary>
+        /// <value>The supported media types.</value>
+        IEnumerable<VideoContentType> SupportedMediaTypes { get; }
+
+        /// <summary>
+        /// Searches the subtitles.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns>
+        Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest request, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the subtitles.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{SubtitleResponse}.</returns>
+        Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken);
+    }
+}

+ 11 - 0
MediaBrowser.Controller/Subtitles/SubtitleResponse.cs

@@ -0,0 +1,11 @@
+using System.IO;
+
+namespace MediaBrowser.Controller.Subtitles
+{
+    public class SubtitleResponse
+    {
+        public string Language { get; set; }
+        public string Format { get; set; }
+        public Stream Stream { get; set; }
+    }
+}

Some files were not shown because too many files changed in this diff