Просмотр исходного кода

Merge pull request #1164 from MediaBrowser/dev

3.0.5724.1
Luke 9 лет назад
Родитель
Сommit
f868dd81e8
100 измененных файлов с 4182 добавлено и 3258 удалено
  1. 2 2
      Emby.Drawing/ImageMagick/StripCollageBuilder.cs
  2. 13 7
      Emby.Drawing/ImageProcessor.cs
  3. 0 6
      MediaBrowser.Api/ApiEntryPoint.cs
  4. 1 2
      MediaBrowser.Api/ConfigurationService.cs
  5. 3 1
      MediaBrowser.Api/EnvironmentService.cs
  6. 203 7
      MediaBrowser.Api/Library/LibraryService.cs
  7. 230 11
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  8. 9 5
      MediaBrowser.Api/MediaBrowser.Api.csproj
  9. 24 5
      MediaBrowser.Api/Movies/MoviesService.cs
  10. 1 44
      MediaBrowser.Api/Movies/TrailersService.cs
  11. 50 0
      MediaBrowser.Api/Music/AlbumsService.cs
  12. 18 1
      MediaBrowser.Api/Music/InstantMixService.cs
  13. 1 1
      MediaBrowser.Api/PackageService.cs
  14. 31 109
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  15. 2 2
      MediaBrowser.Api/Playback/Dash/MpegDashService.cs
  16. 31 0
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  17. 84 148
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  18. 3 3
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  19. 4 1
      MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
  20. 2 2
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  21. 1 1
      MediaBrowser.Api/Playback/StreamState.cs
  22. 259 0
      MediaBrowser.Api/Reports/Activities/ReportActivitiesBuilder.cs
  23. 20 0
      MediaBrowser.Api/Reports/Common/HeaderActivitiesMetadata.cs
  24. 28 2
      MediaBrowser.Api/Reports/Common/HeaderMetadata.cs
  25. 19 13
      MediaBrowser.Api/Reports/Common/ItemViewType.cs
  26. 346 212
      MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs
  27. 14 0
      MediaBrowser.Api/Reports/Common/ReportDisplayType.cs
  28. 128 91
      MediaBrowser.Api/Reports/Common/ReportHelper.cs
  29. 25 0
      MediaBrowser.Api/Reports/Common/ReportIncludeItemTypes.cs
  30. 4 15
      MediaBrowser.Api/Reports/Common/ReportViewType.cs
  31. 490 428
      MediaBrowser.Api/Reports/Data/ReportBuilder.cs
  32. 2 1
      MediaBrowser.Api/Reports/Data/ReportOptions.cs
  33. 0 0
      MediaBrowser.Api/Reports/Model/ReportGroup.cs
  34. 10 0
      MediaBrowser.Api/Reports/Model/ReportHeader.cs
  35. 0 0
      MediaBrowser.Api/Reports/Model/ReportItem.cs
  36. 0 0
      MediaBrowser.Api/Reports/Model/ReportResult.cs
  37. 5 1
      MediaBrowser.Api/Reports/Model/ReportRow.cs
  38. 147 229
      MediaBrowser.Api/Reports/ReportRequests.cs
  39. 662 1135
      MediaBrowser.Api/Reports/ReportsService.cs
  40. 272 202
      MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs
  41. 12 8
      MediaBrowser.Api/SearchService.cs
  42. 76 2
      MediaBrowser.Api/StartupWizardService.cs
  43. 0 6
      MediaBrowser.Api/Sync/SyncHelper.cs
  44. 6 37
      MediaBrowser.Api/UserLibrary/ArtistsService.cs
  45. 45 25
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  46. 243 0
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  47. 15 5
      MediaBrowser.Api/UserLibrary/GameGenresService.cs
  48. 32 3
      MediaBrowser.Api/UserLibrary/GenresService.cs
  49. 10 245
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  50. 2 4
      MediaBrowser.Api/UserLibrary/MusicGenresService.cs
  51. 1 7
      MediaBrowser.Api/UserLibrary/PersonsService.cs
  52. 5 1
      MediaBrowser.Api/UserLibrary/PlaystateService.cs
  53. 1 1
      MediaBrowser.Api/UserLibrary/StudiosService.cs
  54. 1 1
      MediaBrowser.Api/UserLibrary/UserViewsService.cs
  55. 1 1
      MediaBrowser.Api/UserLibrary/YearsService.cs
  56. 16 5
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  57. 2 2
      MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
  58. 2 2
      MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  59. 0 3
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  60. 0 3
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
  61. 1 1
      MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs
  62. 15 4
      MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
  63. 2 2
      MediaBrowser.Common/IO/StreamDefaults.cs
  64. 5 3
      MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs
  65. 5 3
      MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs
  66. 33 4
      MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs
  67. 5 3
      MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs
  68. 4 3
      MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs
  69. 3 1
      MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs
  70. 2 0
      MediaBrowser.Common/Updates/IInstallationManager.cs
  71. 1 2
      MediaBrowser.Controller/Channels/IChannelManager.cs
  72. 10 0
      MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
  73. 1 3
      MediaBrowser.Controller/Dto/IDtoService.cs
  74. 6 0
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  75. 23 1
      MediaBrowser.Controller/Entities/BaseItem.cs
  76. 9 0
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  77. 35 31
      MediaBrowser.Controller/Entities/Folder.cs
  78. 14 0
      MediaBrowser.Controller/Entities/ICollectionFolder.cs
  79. 1 0
      MediaBrowser.Controller/Entities/IHasProgramAttributes.cs
  80. 7 2
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  81. 9 0
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  82. 10 3
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  83. 1 1
      MediaBrowser.Controller/Entities/TV/Episode.cs
  84. 12 1
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  85. 3 3
      MediaBrowser.Controller/Entities/UserView.cs
  86. 56 117
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  87. 51 3
      MediaBrowser.Controller/Library/ILibraryManager.cs
  88. 5 0
      MediaBrowser.Controller/Library/IMetadataFileSaver.cs
  89. 2 2
      MediaBrowser.Controller/Library/IMusicManager.cs
  90. 2 3
      MediaBrowser.Controller/Library/IUserViewManager.cs
  91. 5 1
      MediaBrowser.Controller/LiveTv/ChannelInfo.cs
  92. 15 0
      MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs
  93. 19 0
      MediaBrowser.Controller/LiveTv/IListingsProvider.cs
  94. 55 11
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  95. 3 1
      MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
  96. 55 0
      MediaBrowser.Controller/LiveTv/ITunerHost.cs
  97. 30 0
      MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
  98. 7 1
      MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs
  99. 11 0
      MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
  100. 35 0
      MediaBrowser.Controller/LiveTv/ProgramInfo.cs

+ 2 - 2
Emby.Drawing/ImageMagick/StripCollageBuilder.cs

@@ -285,14 +285,14 @@ namespace Emby.Drawing.ImageMagick
 
         private MagickWand BuildThumbCollageWand(List<string> paths, int width, int height)
         {
-            var inputPaths = ImageHelpers.ProjectPaths(paths, 8);
+            var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
             using (var wandImages = new MagickWand(inputPaths.ToArray()))
             {
                 var wand = new MagickWand(width, height);
                 wand.OpenImage("gradient:#111111-#111111");
                 using (var draw = new DrawingWand())
                 {
-                    var iSlice = Convert.ToInt32(width * .1166666667);
+                    var iSlice = Convert.ToInt32(width * .1166666667 * 2);
                     int iTrans = Convert.ToInt32(height * .25);
                     int iHeight = Convert.ToInt32(height * .62);
                     var horizontalImagePadding = Convert.ToInt32(width * 0.0125);

+ 13 - 7
Emby.Drawing/ImageProcessor.cs

@@ -189,7 +189,7 @@ namespace Emby.Drawing
                 dateModified = tuple.Item2;
             }
 
-            var originalImageSize = GetImageSize(originalImagePath, dateModified);
+            var originalImageSize = GetImageSize(originalImagePath, dateModified, true);
 
             // Determine the output size based on incoming parameters
             var newSize = DrawingUtils.Resize(originalImageSize, options.Width, options.Height, options.MaxWidth, options.MaxHeight);
@@ -363,12 +363,12 @@ namespace Emby.Drawing
         /// <returns>ImageSize.</returns>
         public ImageSize GetImageSize(string path)
         {
-            return GetImageSize(path, File.GetLastWriteTimeUtc(path));
+            return GetImageSize(path, File.GetLastWriteTimeUtc(path), false);
         }
 
         public ImageSize GetImageSize(ItemImageInfo info)
         {
-            return GetImageSize(info.Path, info.DateModified);
+            return GetImageSize(info.Path, info.DateModified, false);
         }
 
         /// <summary>
@@ -376,9 +376,10 @@ namespace Emby.Drawing
         /// </summary>
         /// <param name="path">The path.</param>
         /// <param name="imageDateModified">The image date modified.</param>
+        /// <param name="allowSlowMethod">if set to <c>true</c> [allow slow method].</param>
         /// <returns>ImageSize.</returns>
         /// <exception cref="System.ArgumentNullException">path</exception>
-        private ImageSize GetImageSize(string path, DateTime imageDateModified)
+        private ImageSize GetImageSize(string path, DateTime imageDateModified, bool allowSlowMethod)
         {
             if (string.IsNullOrEmpty(path))
             {
@@ -393,7 +394,7 @@ namespace Emby.Drawing
 
             if (!_cachedImagedSizes.TryGetValue(cacheHash, out size))
             {
-                size = GetImageSizeInternal(path);
+                size = GetImageSizeInternal(path, allowSlowMethod);
 
                 _cachedImagedSizes.AddOrUpdate(cacheHash, size, (keyName, oldValue) => size);
             }
@@ -405,8 +406,9 @@ namespace Emby.Drawing
         /// Gets the image size internal.
         /// </summary>
         /// <param name="path">The path.</param>
+        /// <param name="allowSlowMethod">if set to <c>true</c> [allow slow method].</param>
         /// <returns>ImageSize.</returns>
-        private ImageSize GetImageSizeInternal(string path)
+        private ImageSize GetImageSizeInternal(string path, bool allowSlowMethod)
         {
             ImageSize size;
 
@@ -416,7 +418,11 @@ namespace Emby.Drawing
             }
             catch
             {
-                _logger.Info("Failed to read image header for {0}. Doing it the slow way.", path);
+                if (!allowSlowMethod)
+                {
+                    throw;
+                }
+                //_logger.Info("Failed to read image header for {0}. Doing it the slow way.", path);
 
                 CheckDisposed();
 

+ 0 - 6
MediaBrowser.Api/ApiEntryPoint.cs

@@ -336,12 +336,6 @@ namespace MediaBrowser.Api
             if (job.Type != TranscodingJobType.Progressive)
             {
                 timerDuration = 1800000;
-
-                // We can really reduce the timeout for apps that are using the newer api
-                if (!string.IsNullOrWhiteSpace(job.PlaySessionId))
-                {
-                    timerDuration = 300000;
-                }
             }
 
             job.PingTimeout = timerDuration;

+ 1 - 2
MediaBrowser.Api/ConfigurationService.cs

@@ -7,7 +7,6 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Serialization;
 using ServiceStack;
-using ServiceStack.Text.Controller;
 using ServiceStack.Web;
 using System.Collections.Generic;
 using System.IO;
@@ -26,7 +25,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")]
-    [Authenticated]
+    [Authenticated(AllowBeforeStartupWizard = true)]
     public class GetNamedConfiguration
     {
         [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]

+ 3 - 1
MediaBrowser.Api/EnvironmentService.cs

@@ -221,7 +221,9 @@ namespace MediaBrowser.Api
         /// <returns>IEnumerable{FileSystemEntryInfo}.</returns>
         private IEnumerable<FileSystemEntryInfo> GetFileSystemEntries(GetDirectoryContents request)
         {
-            var entries = new DirectoryInfo(request.Path).EnumerateFileSystemInfos().Where(i =>
+            // using EnumerateFileSystemInfos doesn't handle reparse points (symlinks)
+            var entries = new DirectoryInfo(request.Path).EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
+                .Concat<FileSystemInfo>(new DirectoryInfo(request.Path).EnumerateFiles("*", SearchOption.TopDirectoryOnly)).Where(i =>
             {
                 if (!request.IncludeHidden && i.Attributes.HasFlag(FileAttributes.Hidden))
                 {

+ 203 - 7
MediaBrowser.Api/Library/LibraryService.cs

@@ -1,14 +1,20 @@
-using MediaBrowser.Controller.Activity;
+using MediaBrowser.Api.Movies;
+using MediaBrowser.Api.Music;
+using MediaBrowser.Controller.Activity;
+using MediaBrowser.Controller.Channels;
 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.LiveTv;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.TV;
 using MediaBrowser.Model.Activity;
+using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
@@ -225,6 +231,17 @@ namespace MediaBrowser.Api.Library
         public string TvdbId { get; set; }
     }
 
+    [Route("/Library/Movies/Added", "POST", Summary = "Reports that new movies have been added by an external source")]
+    [Route("/Library/Movies/Updated", "POST", Summary = "Reports that new movies have been added by an external source")]
+    [Authenticated]
+    public class PostUpdatedMovies : IReturnVoid
+    {
+        [ApiMember(Name = "TmdbId", Description = "Tmdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string TmdbId { get; set; }
+        [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string ImdbId { get; set; }
+    }
+
     [Route("/Items/{Id}/Download", "GET", Summary = "Downloads item media")]
     [Authenticated(Roles = "download")]
     public class GetDownload
@@ -237,6 +254,12 @@ namespace MediaBrowser.Api.Library
         public string Id { get; set; }
     }
 
+    [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")]
+    [Authenticated]
+    public class GetSimilarItems : BaseGetSimilarItemsFromItem
+    {
+    }
+
     /// <summary>
     /// Class LibraryService
     /// </summary>
@@ -255,12 +278,16 @@ namespace MediaBrowser.Api.Library
         private readonly IAuthorizationContext _authContext;
         private readonly IActivityManager _activityManager;
         private readonly ILocalizationManager _localization;
+        private readonly ILiveTvManager _liveTv;
+        private readonly IChannelManager _channelManager;
+        private readonly ITVSeriesManager _tvManager;
+        private readonly ILibraryMonitor _libraryMonitor;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryService" /> class.
         /// </summary>
         public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager,
-                              IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization)
+                              IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, IChannelManager channelManager, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor)
         {
             _itemRepo = itemRepo;
             _libraryManager = libraryManager;
@@ -270,6 +297,117 @@ namespace MediaBrowser.Api.Library
             _authContext = authContext;
             _activityManager = activityManager;
             _localization = localization;
+            _liveTv = liveTv;
+            _channelManager = channelManager;
+            _tvManager = tvManager;
+            _libraryMonitor = libraryMonitor;
+        }
+
+        public object Get(GetSimilarItems request)
+        {
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+
+            var item = string.IsNullOrEmpty(request.Id) ?
+                (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
+                _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+
+            if (item is Game)
+            {
+                return new GamesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService)
+                {
+                    AuthorizationContext = AuthorizationContext,
+                    Logger = Logger,
+                    Request = Request,
+                    SessionContext = SessionContext,
+                    ResultFactory = ResultFactory
+
+                }.Get(new GetSimilarGames
+                {
+                    Fields = request.Fields,
+                    Id = request.Id,
+                    Limit = request.Limit,
+                    UserId = request.UserId
+                });
+            }
+            if (item is MusicAlbum)
+            {
+                return new AlbumsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService)
+                {
+                    AuthorizationContext = AuthorizationContext,
+                    Logger = Logger,
+                    Request = Request,
+                    SessionContext = SessionContext,
+                    ResultFactory = ResultFactory
+
+                }.Get(new GetSimilarAlbums
+                {
+                    Fields = request.Fields,
+                    Id = request.Id,
+                    Limit = request.Limit,
+                    UserId = request.UserId
+                });
+            }
+            if (item is MusicArtist)
+            {
+                return new AlbumsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService)
+                {
+                    AuthorizationContext = AuthorizationContext,
+                    Logger = Logger,
+                    Request = Request,
+                    SessionContext = SessionContext,
+                    ResultFactory = ResultFactory
+
+                }.Get(new GetSimilarArtists
+                {
+                    Fields = request.Fields,
+                    Id = request.Id,
+                    Limit = request.Limit,
+                    UserId = request.UserId
+                });
+            }
+
+            var program = item as IHasProgramAttributes;
+            var channelItem = item as ChannelVideoItem;
+
+            if (item is Movie || (program != null && program.IsMovie) || (channelItem != null && channelItem.ContentType == ChannelMediaContentType.Movie) || (channelItem != null && channelItem.ContentType == ChannelMediaContentType.MovieExtra))
+            {
+                return new MoviesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _channelManager)
+                {
+                    AuthorizationContext = AuthorizationContext,
+                    Logger = Logger,
+                    Request = Request,
+                    SessionContext = SessionContext,
+                    ResultFactory = ResultFactory
+
+                }.Get(new GetSimilarMovies
+                {
+                    Fields = request.Fields,
+                    Id = request.Id,
+                    Limit = request.Limit,
+                    UserId = request.UserId
+                });
+            }
+
+            if (item is Series || (program != null && program.IsSeries) || (channelItem != null && channelItem.ContentType == ChannelMediaContentType.Episode))
+            {
+                return new TvShowsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _tvManager)
+                {
+                    AuthorizationContext = AuthorizationContext,
+                    Logger = Logger,
+                    Request = Request,
+                    SessionContext = SessionContext,
+                    ResultFactory = ResultFactory
+
+                }.Get(new GetSimilarShows
+                {
+                    Fields = request.Fields,
+                    Id = request.Id,
+                    Limit = request.Limit,
+                    UserId = request.UserId
+                });
+            }
+
+            return new ItemsResult();
         }
 
         public object Get(GetMediaFolders request)
@@ -297,7 +435,59 @@ namespace MediaBrowser.Api.Library
 
         public void Post(PostUpdatedSeries request)
         {
-            Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None));
+            var series = _libraryManager.GetItems(new InternalItemsQuery
+            {
+                IncludeItemTypes = new[] { typeof(Series).Name }
+
+            }).Items;
+
+            series = series.Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray();
+
+            if (series.Length > 0)
+            {
+                foreach (var item in series)
+                {
+                    _libraryMonitor.ReportFileSystemChanged(item.Path);
+                }
+            }
+            else
+            {
+                Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None));
+            }
+        }
+
+        public void Post(PostUpdatedMovies request)
+        {
+            var movies = _libraryManager.GetItems(new InternalItemsQuery
+            {
+                IncludeItemTypes = new[] { typeof(Movie).Name }
+
+            }).Items;
+
+            if (!string.IsNullOrWhiteSpace(request.ImdbId))
+            {
+                movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToArray();
+            }
+            else if (!string.IsNullOrWhiteSpace(request.TmdbId))
+            {
+                movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToArray();
+            }
+            else
+            {
+                movies = new BaseItem[] { };
+            }
+
+            if (movies.Length > 0)
+            {
+                foreach (var item in movies)
+                {
+                    _libraryMonitor.ReportFileSystemChanged(item.Path);
+                }
+            }
+            else
+            {
+                Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None));
+            }
         }
 
         public object Get(GetDownload request)
@@ -524,7 +714,6 @@ namespace MediaBrowser.Api.Library
         public void Delete(DeleteItem request)
         {
             var item = _libraryManager.GetItemById(request.Id);
-
             var auth = _authContext.GetAuthorizationInfo(Request);
             var user = _userManager.GetUserById(auth.UserId);
 
@@ -533,9 +722,16 @@ namespace MediaBrowser.Api.Library
                 throw new UnauthorizedAccessException();
             }
 
-            var task = _libraryManager.DeleteItem(item);
-
-            Task.WaitAll(task);
+            if (item is ILiveTvRecording)
+            {
+                var task = _liveTv.DeleteRecording(request.Id);
+                Task.WaitAll(task);
+            }
+            else
+            {
+                var task = _libraryManager.DeleteItem(item);
+                Task.WaitAll(task);
+            }
         }
 
         /// <summary>

+ 230 - 11
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Net;
@@ -8,6 +10,7 @@ using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
 using System;
+using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Threading;
@@ -26,7 +29,7 @@ namespace MediaBrowser.Api.LiveTv
 
     [Route("/LiveTv/Channels", "GET", Summary = "Gets available live tv channels.")]
     [Authenticated]
-    public class GetChannels : IReturn<QueryResult<ChannelInfoDto>>
+    public class GetChannels : IReturn<QueryResult<ChannelInfoDto>>, IHasDtoOptions
     {
         [ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public ChannelType? Type { get; set; }
@@ -59,6 +62,30 @@ namespace MediaBrowser.Api.LiveTv
 
         [ApiMember(Name = "EnableFavoriteSorting", Description = "Incorporate favorite and like status into channel sorting.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool EnableFavoriteSorting { get; set; }
+
+        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+        public bool? EnableImages { get; set; }
+
+        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? ImageTypeLimit { get; set; }
+
+        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string EnableImageTypes { get; set; }
+
+        /// <summary>
+        /// Fields to return within the items, in addition to basic information
+        /// </summary>
+        /// <value>The fields.</value>
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Fields { get; set; }
+
+        [ApiMember(Name = "AddCurrentProgram", Description = "Optional. Adds current program info to each channel", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool AddCurrentProgram { get; set; }
+
+        public GetChannels()
+        {
+            AddCurrentProgram = true;
+        }
     }
 
     [Route("/LiveTv/Channels/{Id}", "GET", Summary = "Gets a live tv channel")]
@@ -78,7 +105,7 @@ namespace MediaBrowser.Api.LiveTv
 
     [Route("/LiveTv/Recordings", "GET", Summary = "Gets live tv recordings")]
     [Authenticated]
-    public class GetRecordings : IReturn<QueryResult<BaseItemDto>>
+    public class GetRecordings : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
     {
         [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string ChannelId { get; set; }
@@ -103,6 +130,22 @@ namespace MediaBrowser.Api.LiveTv
 
         [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by recordings belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string SeriesTimerId { get; set; }
+
+        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+        public bool? EnableImages { get; set; }
+
+        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? ImageTypeLimit { get; set; }
+
+        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string EnableImageTypes { get; set; }
+
+        /// <summary>
+        /// Fields to return within the items, in addition to basic information
+        /// </summary>
+        /// <value>The fields.</value>
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Fields { get; set; }
     }
 
     [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")]
@@ -161,7 +204,7 @@ namespace MediaBrowser.Api.LiveTv
 
     [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")]
     [Authenticated]
-    public class GetPrograms : IReturn<QueryResult<BaseItemDto>>
+    public class GetPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
     {
         [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string ChannelIds { get; set; }
@@ -187,6 +230,9 @@ namespace MediaBrowser.Api.LiveTv
         [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
         public bool? IsMovie { get; set; }
 
+        [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsKids { get; set; }
+
         [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
         public bool? IsSports { get; set; }
 
@@ -204,11 +250,27 @@ namespace MediaBrowser.Api.LiveTv
 
         [ApiMember(Name = "Genres", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string Genres { get; set; }
+
+        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+        public bool? EnableImages { get; set; }
+
+        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? ImageTypeLimit { get; set; }
+
+        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string EnableImageTypes { get; set; }
+
+        /// <summary>
+        /// Fields to return within the items, in addition to basic information
+        /// </summary>
+        /// <value>The fields.</value>
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Fields { get; set; }
     }
 
     [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")]
     [Authenticated]
-    public class GetRecommendedPrograms : IReturn<QueryResult<BaseItemDto>>
+    public class GetRecommendedPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
     {
         [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string UserId { get; set; }
@@ -227,6 +289,25 @@ namespace MediaBrowser.Api.LiveTv
 
         [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? IsMovie { get; set; }
+
+        [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsKids { get; set; }
+
+        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+        public bool? EnableImages { get; set; }
+
+        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? ImageTypeLimit { get; set; }
+
+        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string EnableImageTypes { get; set; }
+
+        /// <summary>
+        /// Fields to return within the items, in addition to basic information
+        /// </summary>
+        /// <value>The fields.</value>
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Fields { get; set; }
     }
 
     [Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")]
@@ -330,15 +411,108 @@ namespace MediaBrowser.Api.LiveTv
         public string UserId { get; set; }
     }
 
+    [Route("/LiveTv/TunerHosts", "POST", Summary = "Adds a tuner host")]
+    [Authenticated]
+    public class AddTunerHost : TunerHostInfo, IReturn<TunerHostInfo>
+    {
+    }
+
+    [Route("/LiveTv/TunerHosts", "DELETE", Summary = "Deletes a tuner host")]
+    [Authenticated]
+    public class DeleteTunerHost : IReturnVoid
+    {
+        [ApiMember(Name = "Id", Description = "Tuner host id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+        public string Id { get; set; }
+    }
+
+    [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
+    [Authenticated(AllowBeforeStartupWizard = true)]
+    public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
+    {
+        public bool ValidateLogin { get; set; }
+        public bool ValidateListings { get; set; }
+    }
+
+    [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")]
+    [Authenticated(AllowBeforeStartupWizard = true)]
+    public class DeleteListingProvider : IReturnVoid
+    {
+        [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+        public string Id { get; set; }
+    }
+
+    [Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")]
+    [Authenticated(AllowBeforeStartupWizard = true)]
+    public class GetLineups : IReturn<List<NameIdPair>>
+    {
+        [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Id { get; set; }
+
+        [ApiMember(Name = "Type", Description = "Provider Type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Type { get; set; }
+
+        [ApiMember(Name = "Location", Description = "Location", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Location { get; set; }
+
+        [ApiMember(Name = "Country", Description = "Country", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Country { get; set; }
+    }
+
+    [Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")]
+    [Authenticated(AllowBeforeStartupWizard = true)]
+    public class GetSchedulesDirectCountries
+    {
+    }
+
+    [Route("/LiveTv/Registration", "GET")]
+    [Authenticated]
+    public class GetLiveTvRegistrationInfo : IReturn<MBRegistrationRecord>
+    {
+        [ApiMember(Name = "ChannelId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ChannelId { get; set; }
+
+        [ApiMember(Name = "ProgramId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ProgramId { get; set; }
+
+        [ApiMember(Name = "Feature", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Feature { get; set; }
+    }
+
     public class LiveTvService : BaseApiService
     {
         private readonly ILiveTvManager _liveTvManager;
         private readonly IUserManager _userManager;
+        private readonly IConfigurationManager _config;
+        private readonly IHttpClient _httpClient;
 
-        public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager)
+        public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IConfigurationManager config, IHttpClient httpClient)
         {
             _liveTvManager = liveTvManager;
             _userManager = userManager;
+            _config = config;
+            _httpClient = httpClient;
+        }
+
+        public async Task<object> Get(GetLiveTvRegistrationInfo request)
+        {
+            var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false);
+
+            return ToOptimizedResult(result);
+        }
+
+        public async Task<object> Get(GetSchedulesDirectCountries request)
+        {
+            // https://json.schedulesdirect.org/20141201/available/countries
+
+            var response = await _httpClient.Get(new HttpRequestOptions
+            {
+                Url = "https://json.schedulesdirect.org/20141201/available/countries",
+                CacheLength = TimeSpan.FromDays(1),
+                CacheMode = CacheMode.Unconditional
+
+            }).ConfigureAwait(false);
+
+            return ResultFactory.GetResult(response, "application/json");
         }
 
         private void AssertUserCanManageLiveTv()
@@ -356,6 +530,48 @@ namespace MediaBrowser.Api.LiveTv
             }
         }
 
+        public async Task<object> Post(AddListingProvider request)
+        {
+            var result = await _liveTvManager.SaveListingProvider(request, request.ValidateLogin, request.ValidateListings).ConfigureAwait(false);
+            return ToOptimizedResult(result);
+        }
+
+        public void Delete(DeleteListingProvider request)
+        {
+            var config = GetConfiguration();
+
+            config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
+
+            _config.SaveConfiguration("livetv", config);
+        }
+
+        public async Task<object> Post(AddTunerHost request)
+        {
+            var result = await _liveTvManager.SaveTunerHost(request).ConfigureAwait(false);
+            return ToOptimizedResult(result);
+        }
+
+        public void Delete(DeleteTunerHost request)
+        {
+            var config = GetConfiguration();
+
+            config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
+
+            _config.SaveConfiguration("livetv", config);
+        }
+
+        private LiveTvOptions GetConfiguration()
+        {
+            return _config.GetConfiguration<LiveTvOptions>("livetv");
+        }
+
+        public async Task<object> Get(GetLineups request)
+        {
+            var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false);
+
+            return ToOptimizedSerializedResultUsingCache(info);
+        }
+
         public async Task<object> Get(GetLiveTvInfo request)
         {
             var info = await _liveTvManager.GetLiveTvInfo(CancellationToken.None).ConfigureAwait(false);
@@ -374,9 +590,10 @@ namespace MediaBrowser.Api.LiveTv
                 IsFavorite = request.IsFavorite,
                 IsLiked = request.IsLiked,
                 IsDisliked = request.IsDisliked,
-                EnableFavoriteSorting = request.EnableFavoriteSorting
+                EnableFavoriteSorting = request.EnableFavoriteSorting,
+                AddCurrentProgram = request.AddCurrentProgram
 
-            }, CancellationToken.None).ConfigureAwait(false);
+            }, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
@@ -429,10 +646,11 @@ namespace MediaBrowser.Api.LiveTv
             query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
             query.SortOrder = request.SortOrder;
             query.IsMovie = request.IsMovie;
+            query.IsKids = request.IsKids;
             query.IsSports = request.IsSports;
             query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
 
-            var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false);
+            var result = await _liveTvManager.GetPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
@@ -446,10 +664,11 @@ namespace MediaBrowser.Api.LiveTv
                 Limit = request.Limit,
                 HasAired = request.HasAired,
                 IsMovie = request.IsMovie,
+                IsKids = request.IsKids,
                 IsSports = request.IsSports
             };
 
-            var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false);
+            var result = await _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
@@ -461,7 +680,7 @@ namespace MediaBrowser.Api.LiveTv
 
         public async Task<object> Get(GetRecordings request)
         {
-            var options = new DtoOptions();
+            var options = GetDtoOptions(request);
             options.DeviceId = AuthorizationContext.GetAuthorizationInfo(Request).DeviceId;
 
             var result = await _liveTvManager.GetRecordings(new RecordingQuery

+ 9 - 5
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -84,22 +84,26 @@
     <Compile Include="Playback\MediaInfoService.cs" />
     <Compile Include="Playback\TranscodingThrottler.cs" />
     <Compile Include="PlaylistService.cs" />
+    <Compile Include="Reports\Activities\ReportActivitiesBuilder.cs" />
+    <Compile Include="Reports\Common\HeaderActivitiesMetadata.cs" />
     <Compile Include="Reports\Common\HeaderMetadata.cs" />
     <Compile Include="Reports\Common\ItemViewType.cs" />
     <Compile Include="Reports\Common\ReportBuilderBase.cs" />
+    <Compile Include="Reports\Common\ReportDisplayType.cs" />
     <Compile Include="Reports\Common\ReportExportType.cs" />
     <Compile Include="Reports\Common\ReportFieldType.cs" />
     <Compile Include="Reports\Common\ReportHeaderIdType.cs" />
     <Compile Include="Reports\Common\ReportHelper.cs" />
+    <Compile Include="Reports\Common\ReportIncludeItemTypes.cs" />
     <Compile Include="Reports\Common\ReportViewType.cs" />
     <Compile Include="Reports\Data\ReportBuilder.cs" />
     <Compile Include="Reports\Data\ReportExport.cs" />
-    <Compile Include="Reports\Data\ReportGroup.cs" />
-    <Compile Include="Reports\Data\ReportHeader.cs" />
-    <Compile Include="Reports\Data\ReportItem.cs" />
     <Compile Include="Reports\Data\ReportOptions.cs" />
-    <Compile Include="Reports\Data\ReportResult.cs" />
-    <Compile Include="Reports\Data\ReportRow.cs" />
+    <Compile Include="Reports\Model\ReportGroup.cs" />
+    <Compile Include="Reports\Model\ReportHeader.cs" />
+    <Compile Include="Reports\Model\ReportItem.cs" />
+    <Compile Include="Reports\Model\ReportResult.cs" />
+    <Compile Include="Reports\Model\ReportRow.cs" />
     <Compile Include="Reports\ReportRequests.cs" />
     <Compile Include="Reports\ReportsService.cs" />
     <Compile Include="Reports\Stat\ReportStatBuilder.cs" />

+ 24 - 5
MediaBrowser.Api/Movies/MoviesService.cs

@@ -28,6 +28,14 @@ namespace MediaBrowser.Api.Movies
     {
     }
 
+    /// <summary>
+    /// Class GetSimilarTrailers
+    /// </summary>
+    [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")]
+    public class GetSimilarTrailers : BaseGetSimilarItemsFromItem
+    {
+    }
+
     [Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")]
     public class GetMovieRecommendations : IReturn<RecommendationDto[]>, IHasItemFields
     {
@@ -117,6 +125,17 @@ namespace MediaBrowser.Api.Movies
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
+        public async Task<object> Get(GetSimilarTrailers request)
+        {
+            var result = await GetSimilarItemsResult(
+                // Strip out secondary versions
+                request, item => (item is Movie) && !((Video)item).PrimaryVersionId.HasValue,
+
+                SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+
+            return ToOptimizedSerializedResultUsingCache(result);
+        }
+
         public async Task<object> Get(GetMovieRecommendations request)
         {
             var user = _userManager.GetUserById(request.UserId);
@@ -126,7 +145,7 @@ namespace MediaBrowser.Api.Movies
             movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies);
 
             var listEligibleForCategories = new List<BaseItem>();
-            var listEligibleForSuggestion = new List<BaseItem> ();
+            var listEligibleForSuggestion = new List<BaseItem>();
 
             var list = movies.ToList();
 
@@ -159,7 +178,7 @@ namespace MediaBrowser.Api.Movies
             var dtoOptions = GetDtoOptions(request);
 
             dtoOptions.Fields = request.GetItemFields().ToList();
-            
+
             var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, dtoOptions);
 
             return ToOptimizedResult(result);
@@ -174,14 +193,14 @@ namespace MediaBrowser.Api.Movies
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
 
             Func<BaseItem, bool> filter = i => i.Id != item.Id && includeInSearch(i);
-            
+
             var inputItems = user == null
                                  ? _libraryManager.RootFolder.GetRecursiveChildren(filter)
                                  : user.RootFolder.GetRecursiveChildren(user, filter);
 
             var list = inputItems.ToList();
 
-            if (item is Movie && user != null && user.Configuration.IncludeTrailersInSuggestions)
+            if (user != null && user.Configuration.IncludeTrailersInSuggestions)
             {
                 var trailerResult = await _channelManager.GetAllMediaInternal(new AllChannelMediaQuery
                 {
@@ -224,7 +243,7 @@ namespace MediaBrowser.Api.Movies
             }
 
             var dtoOptions = GetDtoOptions(request);
-          
+
             var result = new ItemsResult
             {
                 Items = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(),

+ 1 - 44
MediaBrowser.Api/Movies/TrailersService.cs

@@ -2,15 +2,12 @@
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
-using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
@@ -18,23 +15,9 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Movies
 {
-    /// <summary>
-    /// Class GetSimilarTrailers
-    /// </summary>
-    [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")]
-    public class GetSimilarTrailers : BaseGetSimilarItemsFromItem
-    {
-    }
-
     [Route("/Trailers", "GET", Summary = "Finds movies and trailers similar to a given trailer.")]
     public class Getrailers : BaseItemsRequest, IReturn<ItemsResult>
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string UserId { get; set; }
     }
 
     /// <summary>
@@ -57,7 +40,6 @@ namespace MediaBrowser.Api.Movies
         /// </summary>
         private readonly ILibraryManager _libraryManager;
 
-        private readonly IItemRepository _itemRepo;
         private readonly IDtoService _dtoService;
         private readonly IChannelManager _channelManager;
 
@@ -67,40 +49,15 @@ namespace MediaBrowser.Api.Movies
         /// <param name="userManager">The user manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
         /// <param name="libraryManager">The library manager.</param>
-        public TrailersService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, IChannelManager channelManager)
+        public TrailersService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IDtoService dtoService, IChannelManager channelManager)
         {
             _userManager = userManager;
             _userDataRepository = userDataRepository;
             _libraryManager = libraryManager;
-            _itemRepo = itemRepo;
             _dtoService = dtoService;
             _channelManager = channelManager;
         }
 
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public object Get(GetSimilarTrailers request)
-        {
-            var dtoOptions = GetDtoOptions(request);
-
-            var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
-                _itemRepo,
-                _libraryManager,
-                _userDataRepository,
-                _dtoService,
-                Logger,
-
-                // Strip out secondary versions
-                request, item => (item is Movie) && !((Video)item).PrimaryVersionId.HasValue,
-
-                SimilarItemsHelper.GetSimiliarityScore);
-
-            return ToOptimizedSerializedResultUsingCache(result);
-        }
-
         public async Task<object> Get(Getrailers request)
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;

+ 50 - 0
MediaBrowser.Api/Music/AlbumsService.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Querying;
 using ServiceStack;
 using System;
 using System.Collections.Generic;
@@ -16,6 +17,11 @@ namespace MediaBrowser.Api.Music
     {
     }
 
+    [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
+    public class GetSimilarArtists : BaseGetSimilarItemsFromItem
+    {
+    }
+
     [Authenticated]
     public class AlbumsService : BaseApiService
     {
@@ -44,6 +50,17 @@ namespace MediaBrowser.Api.Music
             _dtoService = dtoService;
         }
 
+        public object Get(GetSimilarArtists request)
+        {
+            var result = GetSimilarItemsResult(
+
+                request, 
+
+                SimilarItemsHelper.GetSimiliarityScore);
+
+            return ToOptimizedSerializedResultUsingCache(result);
+        }
+        
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -65,6 +82,39 @@ namespace MediaBrowser.Api.Music
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
+        private ItemsResult GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
+        {
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+
+            var item = string.IsNullOrEmpty(request.Id) ?
+                (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
+                _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+
+            var inputItems = _libraryManager.GetArtists(user.RootFolder.GetRecursiveChildren(user, i => i is IHasArtist).OfType<IHasArtist>());
+
+            var list = inputItems.ToList();
+
+            var items = SimilarItemsHelper.GetSimilaritems(item, _libraryManager, list, getSimilarityScore).ToList();
+
+            IEnumerable<BaseItem> returnItems = items;
+
+            if (request.Limit.HasValue)
+            {
+                returnItems = returnItems.Take(request.Limit.Value);
+            }
+
+            var dtoOptions = GetDtoOptions(request);
+
+            var result = new ItemsResult
+            {
+                Items = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(),
+
+                TotalRecordCount = items.Count
+            };
+
+            return result;
+        }
+        
         /// <summary>
         /// Gets the album similarity score.
         /// </summary>

+ 18 - 1
MediaBrowser.Api/Music/InstantMixService.cs

@@ -54,6 +54,11 @@ namespace MediaBrowser.Api.Music
         public string Id { get; set; }
     }
 
+    [Route("/Items/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given item")]
+    public class GetInstantMixFromItem : BaseGetSimilarItemsFromItem
+    {
+    }
+
     [Authenticated]
     public class InstantMixService : BaseApiService
     {
@@ -71,6 +76,17 @@ namespace MediaBrowser.Api.Music
             _libraryManager = libraryManager;
         }
 
+        public object Get(GetInstantMixFromItem request)
+        {
+            var item = _libraryManager.GetItemById(request.Id);
+
+            var user = _userManager.GetUserById(request.UserId);
+
+            var items = _musicManager.GetInstantMixFromItem(item, user);
+
+            return GetResult(items, user, request);
+        }
+
         public object Get(GetInstantMixFromArtistId request)
         {
             var item = _libraryManager.GetItemById(request.Id);
@@ -138,8 +154,9 @@ namespace MediaBrowser.Api.Music
         public object Get(GetInstantMixFromArtist request)
         {
             var user = _userManager.GetUserById(request.UserId);
+            var artist = _libraryManager.GetArtist(request.Name);
 
-            var items = _musicManager.GetInstantMixFromArtist(request.Name, user);
+            var items = _musicManager.GetInstantMixFromArtist(artist, user);
 
             return GetResult(items, user, request);
         }

+ 1 - 1
MediaBrowser.Api/PackageService.cs

@@ -190,7 +190,7 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public async Task<object> Get(GetPackages request)
         {
-            var packages = await _installationManager.GetAvailablePackages(CancellationToken.None, request.PackageType, _appHost.ApplicationVersion).ConfigureAwait(false);
+            var packages = await _installationManager.GetAvailablePackages(CancellationToken.None, false, request.PackageType, _appHost.ApplicationVersion).ConfigureAwait(false);
 
             if (!string.IsNullOrEmpty(request.TargetSystems))
             {

+ 31 - 109
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -263,38 +263,27 @@ namespace MediaBrowser.Api.Playback
             return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
         }
 
-        protected EncodingQuality GetQualitySetting()
-        {
-            var quality = ApiEntryPoint.Instance.GetEncodingOptions().EncodingQuality;
-
-            if (quality == EncodingQuality.Auto)
-            {
-                var cpuCount = Environment.ProcessorCount;
-
-                if (cpuCount >= 4)
-                {
-                    //return EncodingQuality.HighQuality;
-                }
-
-                return EncodingQuality.HighSpeed;
-            }
-
-            return quality;
-        }
-
         /// <summary>
         /// Gets the number of threads.
         /// </summary>
         /// <returns>System.Int32.</returns>
         protected int GetNumberOfThreads(StreamState state, bool isWebm)
         {
+            var threads = ApiEntryPoint.Instance.GetEncodingOptions().EncodingThreadCount;
+
             if (isWebm)
             {
                 // Recommended per docs
                 return Math.Max(Environment.ProcessorCount - 1, 2);
             }
 
-            return 0;
+            // Automatic
+            if (threads == -1)
+            {
+                return 0;
+            }
+
+            return threads;
         }
 
         protected string H264Encoder
@@ -326,77 +315,31 @@ namespace MediaBrowser.Api.Playback
             var isVc1 = state.VideoStream != null &&
                 string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
 
-            var qualitySetting = GetQualitySetting();
-
             if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
             {
                 param = "-preset superfast";
 
-                switch (qualitySetting)
-                {
-                    case EncodingQuality.HighSpeed:
-                        param += " -crf 23";
-                        break;
-                    case EncodingQuality.HighQuality:
-                        param += " -crf 20";
-                        break;
-                    case EncodingQuality.MaxQuality:
-                        param += " -crf 18";
-                        break;
-                }
+                param += " -crf 23";
             }
 
             else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
             {
                 param = "-preset fast";
 
-                switch (qualitySetting)
-                {
-                    case EncodingQuality.HighSpeed:
-                        param += " -crf 28";
-                        break;
-                    case EncodingQuality.HighQuality:
-                        param += " -crf 25";
-                        break;
-                    case EncodingQuality.MaxQuality:
-                        param += " -crf 21";
-                        break;
-                }
+                param += " -crf 28";
             }
 
             // h264 (h264_qsv)
             else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
             {
-                switch (qualitySetting)
-                {
-                    case EncodingQuality.HighSpeed:
-                        param = "-preset 7";
-                        break;
-                    case EncodingQuality.HighQuality:
-                        param = "-preset 4";
-                        break;
-                    case EncodingQuality.MaxQuality:
-                        param = "-preset 1";
-                        break;
-                }
+                param = "-preset 7";
 
             }
 
             // h264 (libnvenc)
             else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
             {
-                switch (qualitySetting)
-                {
-                    case EncodingQuality.HighSpeed:
-                        param = "-preset high-performance";
-                        break;
-                    case EncodingQuality.HighQuality:
-                        param = "";
-                        break;
-                    case EncodingQuality.MaxQuality:
-                        param = "-preset high-quality";
-                        break;
-                }
+                param = "-preset high-performance";
             }
 
             // webm
@@ -409,20 +352,7 @@ namespace MediaBrowser.Api.Playback
                 var qmin = "0";
                 var qmax = "50";
 
-                switch (qualitySetting)
-                {
-                    case EncodingQuality.HighSpeed:
-                        crf = "10";
-                        break;
-                    case EncodingQuality.HighQuality:
-                        crf = "6";
-                        break;
-                    case EncodingQuality.MaxQuality:
-                        crf = "4";
-                        break;
-                    default:
-                        throw new ArgumentException("Unrecognized quality setting");
-                }
+                crf = "10";
 
                 if (isVc1)
                 {
@@ -689,7 +619,7 @@ namespace MediaBrowser.Api.Playback
 
                 // TODO: Perhaps also use original_size=1920x800 ??
                 return string.Format("subtitles=filename='{0}'{1},setpts=PTS -{2}/TB",
-                    subtitlePath.Replace('\\', '/').Replace("'", "\\'").Replace(":/", "\\:/"),
+                    MediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
                     charsetParam,
                     seconds.ToString(UsCulture));
             }
@@ -697,7 +627,7 @@ namespace MediaBrowser.Api.Playback
             var mediaPath = state.MediaPath ?? string.Empty;
 
             return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
-                mediaPath.Replace('\\', '/').Replace("'", "\\'").Replace(":/", "\\:/"),
+                MediaEncoder.EscapeSubtitleFilterPath(mediaPath),
                 state.InternalSubtitleStreamOffset.ToString(UsCulture),
                 seconds.ToString(UsCulture));
         }
@@ -794,7 +724,7 @@ namespace MediaBrowser.Api.Playback
 
                 var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
                     ? 2
-                    : 5;
+                    : 6;
 
                 // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
                 return Math.Min(request.MaxAudioChannels.Value, channelLimit);
@@ -819,11 +749,11 @@ namespace MediaBrowser.Api.Playback
         /// <summary>
         /// Gets the audio encoder.
         /// </summary>
-        /// <param name="request">The request.</param>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        protected string GetAudioEncoder(StreamRequest request)
+        protected string GetAudioEncoder(StreamState state)
         {
-            var codec = request.AudioCodec;
+            var codec = state.OutputAudioCodec;
 
             if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
             {
@@ -848,11 +778,11 @@ namespace MediaBrowser.Api.Playback
         /// <summary>
         /// Gets the name of the output video codec
         /// </summary>
-        /// <param name="request">The request.</param>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        protected string GetVideoEncoder(VideoStreamRequest request)
+        protected string GetVideoEncoder(StreamState state)
         {
-            var codec = request.VideoCodec;
+            var codec = state.OutputVideoCodec;
 
             if (!string.IsNullOrEmpty(codec))
             {
@@ -924,7 +854,7 @@ namespace MediaBrowser.Api.Playback
                 state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
             }
 
-            if (state.MediaSource.RequiresOpening)
+            if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId))
             {
                 var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
                 {
@@ -1704,11 +1634,6 @@ namespace MediaBrowser.Api.Playback
 
         private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
         {
-            if (!EnableStreamCopy)
-            {
-                return;
-            }
-
             if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
             {
                 state.OutputVideoCodec = "copy";
@@ -1720,14 +1645,6 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
-        protected virtual bool EnableStreamCopy
-        {
-            get
-            {
-                return true;
-            }
-        }
-
         private void AttachMediaSourceInfo(StreamState state,
           MediaSourceInfo mediaSource,
           VideoStreamRequest videoRequest,
@@ -1811,13 +1728,18 @@ namespace MediaBrowser.Api.Playback
             state.MediaSource = mediaSource;
         }
 
-        private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
+        protected virtual bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
         {
             if (videoStream.IsInterlaced)
             {
                 return false;
             }
 
+            if (videoStream.IsAnamorphic ?? false)
+            {
+                return false;
+            }
+            
             // Can't stream copy if we're burning in subtitles
             if (request.SubtitleStreamIndex.HasValue)
             {
@@ -1954,7 +1876,7 @@ namespace MediaBrowser.Api.Playback
             return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
         }
 
-        private bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
+        protected virtual bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
         {
             // Source and target codecs must match
             if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))

+ 2 - 2
MediaBrowser.Api/Playback/Dash/MpegDashService.cs

@@ -378,7 +378,7 @@ namespace MediaBrowser.Api.Playback.Dash
 
         protected override string GetAudioArguments(StreamState state)
         {
-            var codec = GetAudioEncoder(state.Request);
+            var codec = GetAudioEncoder(state);
 
             if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
             {
@@ -408,7 +408,7 @@ namespace MediaBrowser.Api.Playback.Dash
 
         protected override string GetVideoArguments(StreamState state)
         {
-            var codec = GetVideoEncoder(state.VideoRequest);
+            var codec = GetVideoEncoder(state);
 
             var args = "-codec:v:0 " + codec;
 

+ 31 - 0
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Net;
@@ -310,5 +311,35 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             return 0;
         }
+
+        protected override bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
+        {
+            if (videoStream.KeyFrames == null || videoStream.KeyFrames.Count == 0)
+            {
+                Logger.Debug("Cannot stream copy video due to missing keyframe info");
+                return false;
+            }
+
+            var previousSegment = 0;
+            foreach (var frame in videoStream.KeyFrames)
+            {
+                var length = frame - previousSegment;
+
+                // Don't allow really long segments because this could result in long download times
+                if (length > 10000)
+                {
+                    Logger.Debug("Cannot stream copy video due to long segment length of {0}ms", length);
+                    return false;
+                }
+                previousSegment = frame;
+            }
+
+            return base.CanStreamCopyVideo(request, videoStream);
+        }
+
+        protected override bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
+        {
+            return false;
+        }
     }
 }

+ 84 - 148
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -13,7 +13,6 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using ServiceStack;
 using System;
-using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
@@ -161,7 +160,6 @@ namespace MediaBrowser.Api.Playback.Hls
             var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
 
             var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex);
-            var segmentLength = state.SegmentLength;
 
             var segmentExtension = GetSegmentFileExtension(state);
 
@@ -170,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Hls
             if (File.Exists(segmentPath))
             {
                 job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
-                return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
+                return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
             }
 
             await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
@@ -179,7 +177,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 if (File.Exists(segmentPath))
                 {
                     job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
-                    return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
+                    return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
                 }
                 else
                 {
@@ -210,14 +208,12 @@ namespace MediaBrowser.Api.Playback.Hls
                         {
                             ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
 
-                            await ReadSegmentLengths(playlistPath).ConfigureAwait(false);
-
                             if (currentTranscodingIndex.HasValue)
                             {
                                 DeleteLastFile(playlistPath, segmentExtension, 0);
                             }
 
-                            request.StartTimeTicks = GetSeekPositionTicks(state, playlistPath, requestedIndex);
+                            request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex);
 
                             job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
                         }
@@ -252,84 +248,76 @@ namespace MediaBrowser.Api.Playback.Hls
 
             Logger.Info("returning {0}", segmentPath);
             job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
-            return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
+            return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
         }
 
-        private static readonly ConcurrentDictionary<string, double> SegmentLengths = new ConcurrentDictionary<string, double>(StringComparer.OrdinalIgnoreCase);
-        private async Task ReadSegmentLengths(string playlist)
-        {
-            try
-            {
-                using (var fileStream = GetPlaylistFileStream(playlist))
-                {
-                    using (var reader = new StreamReader(fileStream))
-                    {
-                        double duration = -1;
-
-                        while (!reader.EndOfStream)
-                        {
-                            var text = await reader.ReadLineAsync().ConfigureAwait(false);
+        // 256k
+        private const int BufferSize = 262144;
 
-                            if (text.StartsWith("#EXTINF", StringComparison.OrdinalIgnoreCase))
-                            {
-                                var parts = text.Split(new[] { ':' }, 2);
-                                if (parts.Length == 2)
-                                {
-                                    var time = parts[1].Trim(new[] { ',' }).Trim();
-                                    double timeValue;
-                                    if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out timeValue))
-                                    {
-                                        duration = timeValue;
-                                        continue;
-                                    }
-                                }
-                            }
-                            else if (duration != -1)
-                            {
-                                SegmentLengths.AddOrUpdate(text, duration, (k, v) => duration);
-                                Logger.Debug("Added segment length of {0} for {1}", duration, text);
-                            }
+        private long GetStartPositionTicks(StreamState state, int requestedIndex)
+        {
+            double startSeconds = 0;
+            var lengths = GetSegmentLengths(state);
 
-                            duration = -1;
-                        }
-                    }
-                }
-            }
-            catch (DirectoryNotFoundException)
+            for (var i = 0; i < requestedIndex; i++)
             {
-
+                startSeconds += lengths[requestedIndex];
             }
-            catch (FileNotFoundException)
-            {
 
-            }
+            var position = TimeSpan.FromSeconds(startSeconds).Ticks;
+            return position;
         }
 
-        private long GetSeekPositionTicks(StreamState state, string playlist, int requestedIndex)
+        private long GetEndPositionTicks(StreamState state, int requestedIndex)
         {
             double startSeconds = 0;
+            var lengths = GetSegmentLengths(state);
 
-            for (var i = 0; i < requestedIndex; i++)
+            for (var i = 0; i <= requestedIndex; i++)
             {
-                var segmentPath = GetSegmentPath(state, playlist, i);
-
-                //double length;
-                //if (SegmentLengths.TryGetValue(Path.GetFileName(segmentPath), out length))
-                //{
-                //    Logger.Debug("Found segment length of {0} for index {1}", length, i);
-                //    startSeconds += length;
-                //}
-                //else
-                //{
-                //    startSeconds += state.SegmentLength;
-                //}
-                startSeconds += state.SegmentLength;
+                startSeconds += lengths[requestedIndex];
             }
 
             var position = TimeSpan.FromSeconds(startSeconds).Ticks;
             return position;
         }
 
+        private double[] GetSegmentLengths(StreamState state)
+        {
+            var result = new List<double>();
+            var encoder = GetVideoEncoder(state);
+
+            if (string.Equals(encoder, "copy", StringComparison.OrdinalIgnoreCase))
+            {
+                var videoStream = state.VideoStream;
+                if (videoStream.KeyFrames != null && videoStream.KeyFrames.Count > 0)
+                {
+                    foreach (var frame in videoStream.KeyFrames)
+                    {
+                        var seconds = TimeSpan.FromMilliseconds(frame).TotalSeconds;
+                        seconds -= result.Sum();
+                        result.Add(seconds);
+                    }
+                    return result.ToArray();
+                }
+            }
+
+            var ticks = state.RunTimeTicks ?? 0;
+
+            var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
+
+            while (ticks > 0)
+            {
+                var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
+
+                result.Add(TimeSpan.FromTicks(length).TotalSeconds);
+
+                ticks -= length;
+            }
+
+            return result.ToArray();
+        }
+
         public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
         {
             var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType);
@@ -434,17 +422,16 @@ namespace MediaBrowser.Api.Playback.Hls
             return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state));
         }
 
-        private async Task<object> GetSegmentResult(string playlistPath,
+        private async Task<object> GetSegmentResult(StreamState state, string playlistPath,
             string segmentPath,
             int segmentIndex,
-            int segmentLength,
             TranscodingJob transcodingJob,
             CancellationToken cancellationToken)
         {
             // If all transcoding has completed, just return immediately
             if (transcodingJob != null && transcodingJob.HasExited && File.Exists(segmentPath))
             {
-                return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
+                return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
             }
 
             var segmentFilename = Path.GetFileName(segmentPath);
@@ -455,21 +442,18 @@ namespace MediaBrowser.Api.Playback.Hls
                 {
                     using (var fileStream = GetPlaylistFileStream(playlistPath))
                     {
-                        using (var reader = new StreamReader(fileStream))
+                        using (var reader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize))
                         {
-                            while (!reader.EndOfStream)
-                            {
-                                var text = await reader.ReadLineAsync().ConfigureAwait(false);
+                            var text = await reader.ReadToEndAsync().ConfigureAwait(false);
 
-                                // If it appears in the playlist, it's done
-                                if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
+                            // If it appears in the playlist, it's done
+                            if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
+                            {
+                                if (File.Exists(segmentPath))
                                 {
-                                    if (File.Exists(segmentPath))
-                                    {
-                                        return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
-                                    }
-                                    break;
+                                    return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
                                 }
+                                //break;
                             }
                         }
                     }
@@ -518,13 +502,12 @@ namespace MediaBrowser.Api.Playback.Hls
             //}
 
             cancellationToken.ThrowIfCancellationRequested();
-            return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
+            return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
         }
 
-        private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob)
+        private object GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob)
         {
-            var segmentEndingSeconds = (1 + index) * segmentLength;
-            var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks;
+            var segmentEndingPositionTicks = GetEndPositionTicks(state, index);
 
             return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
             {
@@ -751,25 +734,23 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
 
+            var segmentLengths = GetSegmentLengths(state);
+
             var builder = new StringBuilder();
 
             builder.AppendLine("#EXTM3U");
             builder.AppendLine("#EXT-X-VERSION:3");
-            builder.AppendLine("#EXT-X-TARGETDURATION:" + (state.SegmentLength).ToString(UsCulture));
+            builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling((segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength)).ToString(UsCulture));
             builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
 
             var queryStringIndex = Request.RawUrl.IndexOf('?');
             var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
 
-            var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
-
             var index = 0;
 
-            while (seconds > 0)
+            foreach (var length in segmentLengths)
             {
-                var length = seconds >= state.SegmentLength ? state.SegmentLength : seconds;
-
-                builder.AppendLine("#EXTINF:" + length.ToString(UsCulture) + ",");
+                builder.AppendLine("#EXTINF:" + length.ToString("0.000000", UsCulture) + ",");
 
                 builder.AppendLine(string.Format("hlsdynamic/{0}/{1}{2}{3}",
 
@@ -778,7 +759,6 @@ namespace MediaBrowser.Api.Playback.Hls
                     GetSegmentFileExtension(isOutputVideo),
                     queryString));
 
-                seconds -= state.SegmentLength;
                 index++;
             }
 
@@ -791,7 +771,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
         protected override string GetAudioArguments(StreamState state)
         {
-            var codec = GetAudioEncoder(state.Request);
+            var codec = GetAudioEncoder(state);
 
             if (!state.IsOutputVideo)
             {
@@ -856,7 +836,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 return string.Empty;
             }
 
-            var codec = GetVideoEncoder(state.VideoRequest);
+            var codec = GetVideoEncoder(state);
 
             var args = "-codec:v:0 " + codec;
 
@@ -877,7 +857,7 @@ namespace MediaBrowser.Api.Playback.Hls
             }
             else
             {
-                var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
+                var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
                     state.SegmentLength.ToString(UsCulture));
 
                 var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
@@ -889,7 +869,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 // Add resolution params, if specified
                 if (!hasGraphicalSubs)
                 {
-                    args += GetOutputSizeParam(state, codec, false);
+                    args += GetOutputSizeParam(state, codec, EnableCopyTs(state));
                 }
 
                 // This is for internal graphical subs
@@ -898,17 +878,17 @@ namespace MediaBrowser.Api.Playback.Hls
                     args += GetGraphicalSubtitleParam(state, codec);
                 }
 
-                args += " -flags +loop-global_header -sc_threshold 0";
-            }
-
-            if (!EnableSplitTranscoding(state))
-            {
-                //args += " -copyts";
+                args += " -flags -global_header -sc_threshold 0";
             }
 
             return args;
         }
 
+        private bool EnableCopyTs(StreamState state)
+        {
+            return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream;
+        }
+
         protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
             var threads = GetNumberOfThreads(state, false);
@@ -921,22 +901,7 @@ namespace MediaBrowser.Api.Playback.Hls
             var toTimeParam = string.Empty;
             var timestampOffsetParam = string.Empty;
 
-            if (EnableSplitTranscoding(state))
-            {
-                var startTime = state.Request.StartTimeTicks ?? 0;
-                var durationSeconds = ApiEntryPoint.Instance.GetEncodingOptions().ThrottleThresholdInSeconds;
-
-                var endTime = startTime + TimeSpan.FromSeconds(durationSeconds).Ticks;
-                endTime = Math.Min(endTime, state.RunTimeTicks.Value);
-
-                if (endTime < state.RunTimeTicks.Value)
-                {
-                    //toTimeParam = " -to " + MediaEncoder.GetTimeParameter(endTime);
-                    toTimeParam = " -t " + MediaEncoder.GetTimeParameter(TimeSpan.FromSeconds(durationSeconds).Ticks);
-                }
-            }
-
-            if (state.IsOutputVideo && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && (state.Request.StartTimeTicks ?? 0) > 0)
+            if (state.IsOutputVideo && !EnableCopyTs(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && (state.Request.StartTimeTicks ?? 0) > 0)
             {
                 timestampOffsetParam = " -output_ts_offset " + MediaEncoder.GetTimeParameter(state.Request.StartTimeTicks ?? 0).ToString(CultureInfo.InvariantCulture);
             }
@@ -978,36 +943,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
         protected override bool EnableThrottling(StreamState state)
         {
-            return !EnableSplitTranscoding(state);
-        }
-
-        private bool EnableSplitTranscoding(StreamState state)
-        {
-            return false;
-            if (string.Equals(Request.QueryString["EnableSplitTranscoding"], "false", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-
-            if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-
-            if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-
-            return state.RunTimeTicks.HasValue && state.IsOutputVideo;
-        }
-
-        protected override bool EnableStreamCopy
-        {
-            get
-            {
-                return false;
-            }
+            return true;
         }
 
         /// <summary>

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

@@ -48,7 +48,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <returns>System.String.</returns>
         protected override string GetAudioArguments(StreamState state)
         {
-            var codec = GetAudioEncoder(state.Request);
+            var codec = GetAudioEncoder(state);
 
             if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
             {
@@ -83,7 +83,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <returns>System.String.</returns>
         protected override string GetVideoArguments(StreamState state)
         {
-            var codec = GetVideoEncoder(state.VideoRequest);
+            var codec = GetVideoEncoder(state);
 
             var args = "-codec:v:0 " + codec;
 
@@ -100,7 +100,7 @@ namespace MediaBrowser.Api.Playback.Hls
                     args;
             }
             
-            var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
+            var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
                 state.SegmentLength.ToString(UsCulture));
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;

+ 4 - 1
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -91,6 +91,9 @@ namespace MediaBrowser.Api.Playback.Progressive
         private readonly IFileSystem _fileSystem;
         private readonly TranscodingJob _job;
 
+        // 256k
+        private const int BufferSize = 262144;
+        
         private long _bytesWritten = 0;
 
         public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job)
@@ -108,7 +111,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             {
                 while (eofCount < 15)
                 {
-                    CopyToInternal(fs, outputStream, 81920);
+                    CopyToInternal(fs, outputStream, BufferSize);
 
                     var fsPosition = fs.Position;
 

+ 2 - 2
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -89,7 +89,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
             // Get the output codec name
-            var videoCodec = GetVideoEncoder(state.VideoRequest);
+            var videoCodec = GetVideoEncoder(state);
 
             var format = string.Empty;
             var keyFrame = string.Empty;
@@ -183,7 +183,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             }
 
             // Get the output codec name
-            var codec = GetAudioEncoder(state.Request);
+            var codec = GetAudioEncoder(state);
 
             var args = "-codec:a:0 " + codec;
 

+ 1 - 1
MediaBrowser.Api/Playback/StreamState.cs

@@ -185,7 +185,7 @@ namespace MediaBrowser.Api.Playback
 
         private async void DisposeLiveStream()
         {
-            if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
+            if ((MediaSource.RequiresClosing) && string.IsNullOrWhiteSpace(Request.LiveStreamId))
             {
                 try
                 {

+ 259 - 0
MediaBrowser.Api/Reports/Activities/ReportActivitiesBuilder.cs

@@ -0,0 +1,259 @@
+using MediaBrowser.Model.Activity;
+using MediaBrowser.Model.Querying;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
+namespace MediaBrowser.Api.Reports
+{
+    /// <summary> A report activities builder. </summary>
+    /// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
+    public class ReportActivitiesBuilder : ReportBuilderBase
+    {
+        #region [Constructors]
+
+        /// <summary>
+        /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportActivitiesBuilder class. </summary>
+        /// <param name="libraryManager"> Manager for library. </param>
+        /// <param name="userManager"> Manager for user. </param>
+        public ReportActivitiesBuilder(ILibraryManager libraryManager, IUserManager userManager)
+            : base(libraryManager)
+        {
+            _userManager = userManager;
+        }
+
+        #endregion
+
+        #region [Private Fields]
+
+        private readonly IUserManager _userManager; ///< Manager for user
+
+        #endregion
+
+        #region [Public Methods]
+
+        /// <summary> Gets a result. </summary>
+        /// <param name="queryResult"> The query result. </param>
+        /// <param name="request"> The request. </param>
+        /// <returns> The result. </returns>
+        public ReportResult GetResult(QueryResult<ActivityLogEntry> queryResult, IReportsQuery request)
+        {
+            ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType);
+            List<ReportOptions<ActivityLogEntry>> options = this.GetReportOptions<ActivityLogEntry>(request,
+                () => this.GetDefaultHeaderMetadata(),
+                (hm) => this.GetOption(hm)).Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType)).ToList();
+
+            var headers = GetHeaders<ActivityLogEntry>(options);
+            var rows = GetReportRows(queryResult.Items, options);
+
+            ReportResult result = new ReportResult { Headers = headers };
+            HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy);
+            int i = headers.FindIndex(x => x.FieldName == groupBy);
+            if (groupBy != HeaderMetadata.None && i >= 0)
+            {
+                var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Group = g.Trim(), Rows = x })
+                    .GroupBy(x => x.Group)
+                    .OrderBy(x => x.Key)
+                    .Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() });
+
+                result.Groups = rowsGroup.ToList();
+                result.IsGrouped = true;
+            }
+            else
+            {
+                result.Rows = rows;
+                result.IsGrouped = false;
+            }
+
+            return result;
+        }
+
+        #endregion
+
+        #region [Protected Internal Methods]
+
+        /// <summary> Gets the headers. </summary>
+        /// <typeparam name="H"> Type of the header. </typeparam>
+        /// <param name="request"> The request. </param>
+        /// <returns> The headers. </returns>
+        /// <seealso cref="M:MediaBrowser.Api.Reports.ReportBuilderBase.GetHeaders{H}(H)"/>
+        protected internal override List<ReportHeader> GetHeaders<H>(H request)
+        {
+            return this.GetHeaders<ActivityLogEntry>(request, () => this.GetDefaultHeaderMetadata(), (hm) => this.GetOption(hm));
+        }
+
+        #endregion
+
+        #region [Private Methods]
+
+        /// <summary> Gets default header metadata. </summary>
+        /// <returns> The default header metadata. </returns>
+        private List<HeaderMetadata> GetDefaultHeaderMetadata()
+        {
+            return new List<HeaderMetadata>
+					{
+                        HeaderMetadata.UserPrimaryImage,
+                        HeaderMetadata.Date,
+                        HeaderMetadata.User,
+                        HeaderMetadata.Type,
+                        HeaderMetadata.Severity,
+						HeaderMetadata.Name,
+                        HeaderMetadata.ShortOverview,
+						HeaderMetadata.Overview,
+                        //HeaderMetadata.UserId
+                        //HeaderMetadata.Item,
+					};
+        }
+
+        /// <summary> Gets an option. </summary>
+        /// <param name="header"> The header. </param>
+        /// <param name="sortField"> The sort field. </param>
+        /// <returns> The option. </returns>
+        private ReportOptions<ActivityLogEntry> GetOption(HeaderMetadata header, string sortField = "")
+        {
+            HeaderMetadata internalHeader = header;
+
+            ReportOptions<ActivityLogEntry> option = new ReportOptions<ActivityLogEntry>()
+            {
+                Header = new ReportHeader
+                {
+                    HeaderFieldType = ReportFieldType.String,
+                    SortField = sortField,
+                    Type = "",
+                    ItemViewType = ItemViewType.None
+                }
+            };
+
+            switch (header)
+            {
+                case HeaderMetadata.Name:
+                    option.Column = (i, r) => i.Name;
+                    option.Header.SortField = "";
+                    break;
+                case HeaderMetadata.Overview:
+                    option.Column = (i, r) => i.Overview;
+                    option.Header.SortField = "";
+                    option.Header.CanGroup = false;
+                    break;
+
+                case HeaderMetadata.ShortOverview:
+                    option.Column = (i, r) => i.ShortOverview;
+                    option.Header.SortField = "";
+                    option.Header.CanGroup = false;
+                    break;
+
+                case HeaderMetadata.Type:
+                    option.Column = (i, r) => i.Type;
+                    option.Header.SortField = "";
+                    break;
+
+                case HeaderMetadata.Date:
+                    option.Column = (i, r) => i.Date;
+                    option.Header.SortField = "";
+                    option.Header.HeaderFieldType = ReportFieldType.DateTime;
+                    option.Header.Type = "";
+                    break;
+
+                case HeaderMetadata.UserPrimaryImage:
+                    //option.Column = (i, r) => i.UserPrimaryImageTag;
+                    option.Header.DisplayType = ReportDisplayType.Screen;
+                    option.Header.ItemViewType = ItemViewType.UserPrimaryImage;
+                    option.Header.ShowHeaderLabel = false;
+                    internalHeader = HeaderMetadata.User;
+                    option.Header.CanGroup = false;
+                    option.Column = (i, r) =>
+                    {
+                        if (!string.IsNullOrEmpty(i.UserId))
+                        {
+                            MediaBrowser.Controller.Entities.User user = _userManager.GetUserById(i.UserId);
+                            if (user != null)
+                            {
+                                var dto = _userManager.GetUserDto(user);
+                                return dto.PrimaryImageTag;
+                            }
+                        }
+                        return string.Empty;
+                    };
+                    option.Header.SortField = "";
+                    break;
+                case HeaderMetadata.Severity:
+                    option.Column = (i, r) => i.Severity;
+                    option.Header.SortField = "";
+                    break;
+                case HeaderMetadata.Item:
+                    option.Column = (i, r) => i.ItemId;
+                    option.Header.SortField = "";
+                    break;
+                case HeaderMetadata.User:
+                    option.Column = (i, r) =>
+                    {
+                        if (!string.IsNullOrEmpty(i.UserId))
+                        {
+                            MediaBrowser.Controller.Entities.User user = _userManager.GetUserById(i.UserId);
+                            if (user != null)
+                                return user.Name;
+                        }
+                        return string.Empty;
+                    };
+                    option.Header.SortField = "";
+                    break;
+                case HeaderMetadata.UserId:
+                    option.Column = (i, r) => i.UserId;
+                    option.Header.SortField = "";
+                    break;
+            }
+
+            option.Header.Name = GetLocalizedHeader(internalHeader);
+            option.Header.FieldName = header;
+
+            return option;
+        }
+
+        /// <summary> Gets report rows. </summary>
+        /// <param name="items"> The items. </param>
+        /// <param name="options"> Options for controlling the operation. </param>
+        /// <returns> The report rows. </returns>
+        private List<ReportRow> GetReportRows(IEnumerable<ActivityLogEntry> items, List<ReportOptions<ActivityLogEntry>> options)
+        {
+            var rows = new List<ReportRow>();
+
+            foreach (ActivityLogEntry item in items)
+            {
+                ReportRow rRow = GetRow(item);
+                foreach (ReportOptions<ActivityLogEntry> option in options)
+                {
+                    object itemColumn = option.Column != null ? option.Column(item, rRow) : "";
+                    object itemId = option.ItemID != null ? option.ItemID(item) : "";
+                    ReportItem rItem = new ReportItem
+                    {
+                        Name = ReportHelper.ConvertToString(itemColumn, option.Header.HeaderFieldType),
+                        Id = ReportHelper.ConvertToString(itemId, ReportFieldType.Object)
+                    };
+                    rRow.Columns.Add(rItem);
+                }
+
+                rows.Add(rRow);
+            }
+
+            return rows;
+        }
+
+        /// <summary> Gets a row. </summary>
+        /// <param name="item"> The item. </param>
+        /// <returns> The row. </returns>
+        private ReportRow GetRow(ActivityLogEntry item)
+        {
+            ReportRow rRow = new ReportRow
+            {
+                Id = item.Id,
+                UserId = item.UserId
+            };
+            return rRow;
+        }
+
+        #endregion
+
+    }
+}

+ 20 - 0
MediaBrowser.Api/Reports/Common/HeaderActivitiesMetadata.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+    public enum HeaderActivitiesMetadata
+	{
+        None,
+        Name,
+        Overview,
+        ShortOverview,
+        Type,
+        Date,
+        UserPrimaryImageTag,
+        Severity,
+        Item,
+        User
+	}
+}

+ 28 - 2
MediaBrowser.Api/Reports/Common/HeaderMetadata.cs

@@ -35,13 +35,39 @@ namespace MediaBrowser.Api.Reports
 		Subtitles,
 		Genres,
 		Countries,
-		StatusImage,
+        Status,
 		Tracks,
 		EpisodeSeries,
 		EpisodeSeason,
 		AudioAlbumArtist,
 		MusicArtist,
 		AudioAlbum,
-		Status
+        Locked,
+        Unidentified,
+        ImagePrimary,
+        ImageBackdrop,
+        ImageLogo,
+        Actor, 
+        Studios,
+        Composer, 
+        Director, 
+        GuestStar, 
+        Producer, 
+        Writer,
+        Artist,
+        Years,
+        ParentalRatings,
+        CommunityRatings,
+
+        //Activity logs
+        Overview,
+        ShortOverview,
+        Type,
+        Date,
+        UserPrimaryImage,
+        Severity,
+        Item,
+        User,
+        UserId
 	}
 }

+ 19 - 13
MediaBrowser.Api/Reports/Common/ItemViewType.cs

@@ -4,17 +4,23 @@ using System.Linq;
 
 namespace MediaBrowser.Api.Reports
 {
-	public enum ItemViewType
-	{
-		None,
-		Detail,
-		Edit,
-		List,
-		ItemByNameDetails,
-		StatusImage,
-		EmbeddedImage,
-		SubtitleImage,
-		TrailersImage,
-		SpecialsImage
-	}
+    public enum ItemViewType
+    {
+        None,
+        Detail,
+        Edit,
+        List,
+        ItemByNameDetails,
+        StatusImage,
+        EmbeddedImage,
+        SubtitleImage,
+        TrailersImage,
+        SpecialsImage,
+        LockDataImage,
+        UnidentifiedImage,
+        TagsPrimaryImage,
+        TagsBackdropImage,
+        TagsLogoImage,
+        UserPrimaryImage
+    }
 }

+ 346 - 212
MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs

@@ -13,217 +13,351 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Reports
 {
-	/// <summary> A report builder base. </summary>
-	public class ReportBuilderBase
-	{
-		/// <summary>
-		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilderBase class. </summary>
-		/// <param name="libraryManager"> Manager for library. </param>
-		public ReportBuilderBase(ILibraryManager libraryManager)
-		{
-			_libraryManager = libraryManager;
-		}
-
-		/// <summary> Manager for library. </summary>
-		protected readonly ILibraryManager _libraryManager;
-
-		/// <summary> Gets audio stream. </summary>
-		/// <param name="item"> The item. </param>
-		/// <returns> The audio stream. </returns>
-		protected string GetAudioStream(BaseItem item)
-		{
-			var stream = GetStream(item, MediaStreamType.Audio);
-			if (stream != null)
-				return stream.Codec.ToUpper() == "DCA" ? stream.Profile : stream.Codec.
-				ToUpper();
-
-			return string.Empty;
-		}
-
-		/// <summary> Gets an episode. </summary>
-		/// <param name="item"> The item. </param>
-		/// <returns> The episode. </returns>
-		protected string GetEpisode(BaseItem item)
-		{
-
-			if (item.GetClientTypeName() == ChannelMediaContentType.Episode.ToString() && item.ParentIndexNumber != null)
-				return "Season " + item.ParentIndexNumber;
-			else
-				return item.Name;
-		}
-
-		/// <summary> Gets a genre. </summary>
-		/// <param name="name"> The name. </param>
-		/// <returns> The genre. </returns>
-		protected Genre GetGenre(string name)
-		{
-			if (string.IsNullOrEmpty(name))
-				return null;
-			return _libraryManager.GetGenre(name);
-		}
-
-		/// <summary> Gets genre identifier. </summary>
-		/// <param name="name"> The name. </param>
-		/// <returns> The genre identifier. </returns>
-		protected string GetGenreID(string name)
-		{
-			if (string.IsNullOrEmpty(name))
-				return string.Empty;
-			return string.Format("{0:N}",
-					GetGenre(name).Id);
-		}
-
-		/// <summary> Gets list as string. </summary>
-		/// <param name="items"> The items. </param>
-		/// <returns> The list as string. </returns>
-		protected string GetListAsString(List<string> items)
-		{
-			return String.Join("; ", items);
-		}
-
-		/// <summary> Gets media source information. </summary>
-		/// <param name="item"> The item. </param>
-		/// <returns> The media source information. </returns>
-		protected MediaSourceInfo GetMediaSourceInfo(BaseItem item)
-		{
-			var mediaSource = item as IHasMediaSources;
-			if (mediaSource != null)
-				return mediaSource.GetMediaSources(false).FirstOrDefault(n => n.Type == MediaSourceType.Default);
-
-			return null;
-		}
-
-		/// <summary> Gets an object. </summary>
-		/// <typeparam name="T"> Generic type parameter. </typeparam>
-		/// <typeparam name="R"> Type of the r. </typeparam>
-		/// <param name="item"> The item. </param>
-		/// <param name="function"> The function. </param>
-		/// <param name="defaultValue"> The default value. </param>
-		/// <returns> The object. </returns>
-		protected R GetObject<T, R>(BaseItem item, Func<T, R> function, R defaultValue = default(R)) where T : class
-		{
-			var value = item as T;
-			if (value != null && function != null)
-				return function(value);
-			else
-				return defaultValue;
-		}
-
-		/// <summary> Gets a person. </summary>
-		/// <param name="name"> The name. </param>
-		/// <returns> The person. </returns>
-		protected Person GetPerson(string name)
-		{
-			if (string.IsNullOrEmpty(name))
-				return null;
-			return _libraryManager.GetPerson(name);
-		}
-
-		/// <summary> Gets person identifier. </summary>
-		/// <param name="name"> The name. </param>
-		/// <returns> The person identifier. </returns>
-		protected string GetPersonID(string name)
-		{
-			if (string.IsNullOrEmpty(name))
-				return string.Empty;
-			return string.Format("{0:N}",
-					GetPerson(name).Id);
-		}
-
-		/// <summary> Gets runtime date time. </summary>
-		/// <param name="runtime"> The runtime. </param>
-		/// <returns> The runtime date time. </returns>
-		protected double? GetRuntimeDateTime(long? runtime)
-		{
-			if (runtime.HasValue)
+    /// <summary> A report builder base. </summary>
+    public abstract class ReportBuilderBase
+    {
+
+        #region [Constructors]
+
+        /// <summary>
+        /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilderBase class. </summary>
+        /// <param name="libraryManager"> Manager for library. </param>
+        public ReportBuilderBase(ILibraryManager libraryManager)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        #endregion
+
+        #region [Protected Fields]
+
+        /// <summary> Manager for library. </summary>
+        protected readonly ILibraryManager _libraryManager; ///< Manager for library
+
+        protected Func<bool, string> GetBoolString = s => s == true ? "x" : ""; ///< .
+
+        #endregion
+
+        #region [Protected Internal Methods]
+
+        /// <summary> Gets the headers. </summary>
+        /// <typeparam name="H"> Type of the header. </typeparam>
+        /// <param name="request"> The request. </param>
+        /// <returns> The headers. </returns>
+        protected internal abstract List<ReportHeader> GetHeaders<H>(H request) where H : IReportsHeader;
+
+        #endregion
+
+        #region [Protected Methods]
+
+        /// <summary> Gets active headers. </summary>
+        /// <typeparam name="T"> Generic type parameter. </typeparam>
+        /// <param name="options"> Options for controlling the operation. </param>
+        /// <returns> The active headers. </returns>
+        protected List<ReportHeader> GetActiveHeaders<T>(List<ReportOptions<T>> options, ReportDisplayType displayType)
+        {
+            List<ReportHeader> headers = new List<ReportHeader>();
+            foreach (ReportOptions<T> option in options.Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType)))
+            {
+                headers.Add(option.Header);
+            }
+
+            return headers;
+        }
+
+        /// <summary> Gets audio stream. </summary>
+        /// <param name="item"> The item. </param>
+        /// <returns> The audio stream. </returns>
+        protected string GetAudioStream(BaseItem item)
+        {
+            var stream = GetStream(item, MediaStreamType.Audio);
+            if (stream != null)
+                return stream.Codec.ToUpper() == "DCA" ? stream.Profile : stream.Codec.
+                ToUpper();
+
+            return string.Empty;
+        }
+
+        /// <summary> Gets an episode. </summary>
+        /// <param name="item"> The item. </param>
+        /// <returns> The episode. </returns>
+        protected string GetEpisode(BaseItem item)
+        {
+
+            if (item.GetClientTypeName() == ChannelMediaContentType.Episode.ToString() && item.ParentIndexNumber != null)
+                return "Season " + item.ParentIndexNumber;
+            else
+                return item.Name;
+        }
+
+        /// <summary> Gets a genre. </summary>
+        /// <param name="name"> The name. </param>
+        /// <returns> The genre. </returns>
+        protected Genre GetGenre(string name)
+        {
+            if (string.IsNullOrEmpty(name))
+                return null;
+            return _libraryManager.GetGenre(name);
+        }
+
+        /// <summary> Gets genre identifier. </summary>
+        /// <param name="name"> The name. </param>
+        /// <returns> The genre identifier. </returns>
+        protected string GetGenreID(string name)
+        {
+            if (string.IsNullOrEmpty(name))
+                return string.Empty;
+            return string.Format("{0:N}",
+                    GetGenre(name).Id);
+        }
+
+        /// <summary> Gets the headers. </summary>
+        /// <typeparam name="T"> Generic type parameter. </typeparam>
+        /// <param name="options"> Options for controlling the operation. </param>
+        /// <returns> The headers. </returns>
+        protected List<ReportHeader> GetHeaders<T>(List<ReportOptions<T>> options)
+        {
+            List<ReportHeader> headers = new List<ReportHeader>();
+            foreach (ReportOptions<T> option in options)
+            {
+                headers.Add(option.Header);
+            }
+
+            return headers;
+        }
+
+        /// <summary> Gets the headers. </summary>
+        /// <typeparam name="T"> Generic type parameter. </typeparam>
+        /// <param name="request"> The request. </param>
+        /// <param name="getHeadersMetadata"> The get headers metadata. </param>
+        /// <param name="getOptions"> Options for controlling the get. </param>
+        /// <returns> The headers. </returns>
+        protected List<ReportHeader> GetHeaders<T>(IReportsHeader request, Func<List<HeaderMetadata>> getHeadersMetadata, Func<HeaderMetadata, ReportOptions<T>> getOptions)
+        {
+            List<ReportOptions<T>> options = this.GetReportOptions(request, getHeadersMetadata, getOptions);
+            return this.GetHeaders(options);
+        }
+
+        /// <summary> Gets list as string. </summary>
+        /// <param name="items"> The items. </param>
+        /// <returns> The list as string. </returns>
+        protected string GetListAsString(List<string> items)
+        {
+            return String.Join("; ", items);
+        }
+
+        /// <summary> Gets localized header. </summary>
+        /// <param name="internalHeader"> The internal header. </param>
+        /// <returns> The localized header. </returns>
+        protected static string GetLocalizedHeader(HeaderMetadata internalHeader)
+        {
+            string headerName = "";
+            if (internalHeader != HeaderMetadata.None)
+            {
+                string localHeader = "Header" + internalHeader.ToString();
+                headerName = ReportHelper.GetCoreLocalizedString(localHeader);
+            }
+            return headerName;
+        }
+
+        /// <summary> Gets media source information. </summary>
+        /// <param name="item"> The item. </param>
+        /// <returns> The media source information. </returns>
+        protected MediaSourceInfo GetMediaSourceInfo(BaseItem item)
+        {
+            var mediaSource = item as IHasMediaSources;
+            if (mediaSource != null)
+                return mediaSource.GetMediaSources(false).FirstOrDefault(n => n.Type == MediaSourceType.Default);
+
+            return null;
+        }
+
+        /// <summary> Gets an object. </summary>
+        /// <typeparam name="T"> Generic type parameter. </typeparam>
+        /// <typeparam name="R"> Type of the r. </typeparam>
+        /// <param name="item"> The item. </param>
+        /// <param name="function"> The function. </param>
+        /// <param name="defaultValue"> The default value. </param>
+        /// <returns> The object. </returns>
+        protected R GetObject<T, R>(BaseItem item, Func<T, R> function, R defaultValue = default(R)) where T : class
+        {
+            var value = item as T;
+            if (value != null && function != null)
+                return function(value);
+            else
+                return defaultValue;
+        }
+
+        /// <summary> Gets a person. </summary>
+        /// <param name="name"> The name. </param>
+        /// <returns> The person. </returns>
+        protected Person GetPerson(string name)
+        {
+            if (string.IsNullOrEmpty(name))
+                return null;
+            return _libraryManager.GetPerson(name);
+        }
+
+        /// <summary> Gets person identifier. </summary>
+        /// <param name="name"> The name. </param>
+        /// <returns> The person identifier. </returns>
+        protected string GetPersonID(string name)
+        {
+            if (string.IsNullOrEmpty(name))
+                return string.Empty;
+            return string.Format("{0:N}",
+                    GetPerson(name).Id);
+        }
+
+        /// <summary> Gets report options. </summary>
+        /// <typeparam name="T"> Generic type parameter. </typeparam>
+        /// <param name="request"> The request. </param>
+        /// <param name="getHeadersMetadata"> The get headers metadata. </param>
+        /// <param name="getOptions"> Options for controlling the get. </param>
+        /// <returns> The report options. </returns>
+        protected List<ReportOptions<T>> GetReportOptions<T>(IReportsHeader request, Func<List<HeaderMetadata>> getHeadersMetadata, Func<HeaderMetadata, ReportOptions<T>> getOptions)
+        {
+            List<HeaderMetadata> headersMetadata = getHeadersMetadata();
+            List<ReportOptions<T>> options = new List<ReportOptions<T>>();
+            ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType);
+            foreach (HeaderMetadata header in headersMetadata)
+            {
+                ReportOptions<T> headerOptions = getOptions(header);
+                if (this.DisplayTypeVisible(headerOptions.Header.DisplayType, displayType))
+                    options.Add(headerOptions);
+            }
+
+            if (request != null && !string.IsNullOrEmpty(request.ReportColumns))
+            {
+                List<HeaderMetadata> headersMetadataFiltered = ReportHelper.GetFilteredReportHeaderMetadata(request.ReportColumns, () => headersMetadata);
+                foreach (ReportHeader header in options.Select(x => x.Header))
+                {
+
+                    if (this.DisplayTypeVisible(header.DisplayType, displayType))
+                    {
+                       
+                        if (!headersMetadataFiltered.Contains(header.FieldName) && displayType != ReportDisplayType.Export)
+                        {
+                            header.DisplayType = ReportDisplayType.None;
+                        }
+                    }
+                    else
+                        header.DisplayType = ReportDisplayType.None;
+                }
+            }
+
+            return options;
+        }
+
+        /// <summary> Gets runtime date time. </summary>
+        /// <param name="runtime"> The runtime. </param>
+        /// <returns> The runtime date time. </returns>
+        protected double? GetRuntimeDateTime(long? runtime)
+        {
+            if (runtime.HasValue)
                 return Math.Ceiling(new TimeSpan(runtime.Value).TotalMinutes);
-			return null;
-		}
-
-		/// <summary> Gets series production year. </summary>
-		/// <param name="item"> The item. </param>
-		/// <returns> The series production year. </returns>
-		protected string GetSeriesProductionYear(BaseItem item)
-		{
-
-			string productionYear = item.ProductionYear.ToString();
-			var series = item as Series;
-			if (series == null)
-			{
-				if (item.ProductionYear == null || item.ProductionYear == 0)
-					return string.Empty;
-				return productionYear;
-			}
-
-			if (series.Status == SeriesStatus.Continuing)
-				return productionYear += "-Present";
-
-			if (series.EndDate != null && series.EndDate.Value.Year != series.ProductionYear)
-				return productionYear += "-" + series.EndDate.Value.Year;
-
-			return productionYear;
-		}
-
-		/// <summary> Gets a stream. </summary>
-		/// <param name="item"> The item. </param>
-		/// <param name="streamType"> Type of the stream. </param>
-		/// <returns> The stream. </returns>
-		protected MediaStream GetStream(BaseItem item, MediaStreamType streamType)
-		{
-			var itemInfo = GetMediaSourceInfo(item);
-			if (itemInfo != null)
-				return itemInfo.MediaStreams.FirstOrDefault(n => n.Type == streamType);
-
-			return null;
-		}
-
-		/// <summary> Gets a studio. </summary>
-		/// <param name="name"> The name. </param>
-		/// <returns> The studio. </returns>
-		protected Studio GetStudio(string name)
-		{
-			if (string.IsNullOrEmpty(name))
-				return null;
-			return _libraryManager.GetStudio(name);
-		}
-
-		/// <summary> Gets studio identifier. </summary>
-		/// <param name="name"> The name. </param>
-		/// <returns> The studio identifier. </returns>
-		protected string GetStudioID(string name)
-		{
-			if (string.IsNullOrEmpty(name))
-				return string.Empty;
-			return string.Format("{0:N}",
-					GetStudio(name).Id);
-		}
-
-		/// <summary> Gets video resolution. </summary>
-		/// <param name="item"> The item. </param>
-		/// <returns> The video resolution. </returns>
-		protected string GetVideoResolution(BaseItem item)
-		{
-			var stream = GetStream(item,
-					MediaStreamType.Video);
-			if (stream != null && stream.Width != null)
-				return string.Format("{0} * {1}",
-						stream.Width,
-						(stream.Height != null ? stream.Height.ToString() : "-"));
-
-			return string.Empty;
-		}
-
-		/// <summary> Gets video stream. </summary>
-		/// <param name="item"> The item. </param>
-		/// <returns> The video stream. </returns>
-		protected string GetVideoStream(BaseItem item)
-		{
-			var stream = GetStream(item, MediaStreamType.Video);
-			if (stream != null)
-				return stream.Codec.ToUpper();
-
-			return string.Empty;
-		}
-
-	}
+            return null;
+        }
+
+        /// <summary> Gets series production year. </summary>
+        /// <param name="item"> The item. </param>
+        /// <returns> The series production year. </returns>
+        protected string GetSeriesProductionYear(BaseItem item)
+        {
+
+            string productionYear = item.ProductionYear.ToString();
+            var series = item as Series;
+            if (series == null)
+            {
+                if (item.ProductionYear == null || item.ProductionYear == 0)
+                    return string.Empty;
+                return productionYear;
+            }
+
+            if (series.Status == SeriesStatus.Continuing)
+                return productionYear += "-Present";
+
+            if (series.EndDate != null && series.EndDate.Value.Year != series.ProductionYear)
+                return productionYear += "-" + series.EndDate.Value.Year;
+
+            return productionYear;
+        }
+
+        /// <summary> Gets a stream. </summary>
+        /// <param name="item"> The item. </param>
+        /// <param name="streamType"> Type of the stream. </param>
+        /// <returns> The stream. </returns>
+        protected MediaStream GetStream(BaseItem item, MediaStreamType streamType)
+        {
+            var itemInfo = GetMediaSourceInfo(item);
+            if (itemInfo != null)
+                return itemInfo.MediaStreams.FirstOrDefault(n => n.Type == streamType);
+
+            return null;
+        }
+
+        /// <summary> Gets a studio. </summary>
+        /// <param name="name"> The name. </param>
+        /// <returns> The studio. </returns>
+        protected Studio GetStudio(string name)
+        {
+            if (string.IsNullOrEmpty(name))
+                return null;
+            return _libraryManager.GetStudio(name);
+        }
+
+        /// <summary> Gets studio identifier. </summary>
+        /// <param name="name"> The name. </param>
+        /// <returns> The studio identifier. </returns>
+        protected string GetStudioID(string name)
+        {
+            if (string.IsNullOrEmpty(name))
+                return string.Empty;
+            return string.Format("{0:N}",
+                    GetStudio(name).Id);
+        }
+
+        /// <summary> Gets video resolution. </summary>
+        /// <param name="item"> The item. </param>
+        /// <returns> The video resolution. </returns>
+        protected string GetVideoResolution(BaseItem item)
+        {
+            var stream = GetStream(item,
+                    MediaStreamType.Video);
+            if (stream != null && stream.Width != null)
+                return string.Format("{0} * {1}",
+                        stream.Width,
+                        (stream.Height != null ? stream.Height.ToString() : "-"));
+
+            return string.Empty;
+        }
+
+        /// <summary> Gets video stream. </summary>
+        /// <param name="item"> The item. </param>
+        /// <returns> The video stream. </returns>
+        protected string GetVideoStream(BaseItem item)
+        {
+            var stream = GetStream(item, MediaStreamType.Video);
+            if (stream != null)
+                return stream.Codec.ToUpper();
+
+            return string.Empty;
+        }
+
+        /// <summary> Displays a type visible. </summary>
+        /// <param name="headerDisplayType"> Type of the header display. </param>
+        /// <param name="displayType"> Type of the display. </param>
+        /// <returns> true if it succeeds, false if it fails. </returns>
+        protected bool DisplayTypeVisible(ReportDisplayType headerDisplayType, ReportDisplayType displayType)
+        {
+            if (headerDisplayType == ReportDisplayType.None)
+                return false;
+
+            bool rval = headerDisplayType == displayType || headerDisplayType == ReportDisplayType.ScreenExport && (displayType == ReportDisplayType.Screen || displayType == ReportDisplayType.Export);
+            return rval;
+        }
+
+        #endregion
+
+    }
 }

+ 14 - 0
MediaBrowser.Api/Reports/Common/ReportDisplayType.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+    public enum ReportDisplayType
+    {
+		None,
+		Screen,
+		Export,
+        ScreenExport 
+	}
+}

+ 128 - 91
MediaBrowser.Api/Reports/Common/ReportHelper.cs

@@ -2,100 +2,137 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Reports
 {
-	public class ReportHelper
-	{
-		/// <summary> Gets java script localized string. </summary>
-		/// <param name="phrase"> The phrase. </param>
-		/// <returns> The java script localized string. </returns>
-		public static string GetJavaScriptLocalizedString(string phrase)
-		{
-			var dictionary = BaseItem.LocalizationManager.GetJavaScriptLocalizationDictionary(BaseItem.ConfigurationManager.Configuration.UICulture);
-
-			string value;
-
-			if (dictionary.TryGetValue(phrase, out value))
-			{
-				return value;
-			}
-
-			return phrase;
-		}
-
-		/// <summary> Gets server localized string. </summary>
-		/// <param name="phrase"> The phrase. </param>
-		/// <returns> The server localized string. </returns>
-		public static string GetServerLocalizedString(string phrase)
-		{
-			return BaseItem.LocalizationManager.GetLocalizedString(phrase, BaseItem.ConfigurationManager.Configuration.UICulture);
-		}
-
-		/// <summary> Gets row type. </summary>
-		/// <param name="rowType"> The type. </param>
-		/// <returns> The row type. </returns>
-		public static ReportViewType GetRowType(string rowType)
-		{
-			if (string.IsNullOrEmpty(rowType))
-				return ReportViewType.BaseItem;
-
-			ReportViewType rType;
-
-			if (!Enum.TryParse<ReportViewType>(rowType, out rType))
-				return ReportViewType.BaseItem;
-
-			return rType;
-		}
-
-		/// <summary> Gets header metadata type. </summary>
-		/// <param name="header"> The header. </param>
-		/// <returns> The header metadata type. </returns>
-		public static HeaderMetadata GetHeaderMetadataType(string header)
-		{
-			if (string.IsNullOrEmpty(header))
-				return HeaderMetadata.None;
-
-			HeaderMetadata rType;
-
-			if (!Enum.TryParse<HeaderMetadata>(header, out rType))
-				return HeaderMetadata.None;
-
-			return rType;
-		}
-
-		/// <summary> Convert field to string. </summary>
-		/// <typeparam name="T"> Generic type parameter. </typeparam>
-		/// <param name="value"> The value. </param>
-		/// <param name="fieldType"> Type of the field. </param>
-		/// <returns> The field converted to string. </returns>
-		public static string ConvertToString<T>(T value, ReportFieldType fieldType)
-		{
-			if (value == null)
-				return "";
-			switch (fieldType)
-			{
-				case ReportFieldType.String:
-					return value.ToString();
-				case ReportFieldType.Boolean:
-					return value.ToString();
-				case ReportFieldType.Date:
-					return string.Format("{0:d}", value);
-				case ReportFieldType.Time:
-					return string.Format("{0:t}", value);
-				case ReportFieldType.DateTime:
-					return string.Format("{0:d}", value);
+    /// <summary> A report helper. </summary>
+    public class ReportHelper
+    {
+        #region [Public Methods]
+
+        /// <summary> Convert field to string. </summary>
+        /// <typeparam name="T"> Generic type parameter. </typeparam>
+        /// <param name="value"> The value. </param>
+        /// <param name="fieldType"> Type of the field. </param>
+        /// <returns> The field converted to string. </returns>
+        public static string ConvertToString<T>(T value, ReportFieldType fieldType)
+        {
+            if (value == null)
+                return "";
+            switch (fieldType)
+            {
+                case ReportFieldType.String:
+                    return value.ToString();
+                case ReportFieldType.Boolean:
+                    return value.ToString();
+                case ReportFieldType.Date:
+                    return string.Format("{0:d}", value);
+                case ReportFieldType.Time:
+                    return string.Format("{0:t}", value);
+                case ReportFieldType.DateTime:
+                    return string.Format("{0:d}", value);
                 case ReportFieldType.Minutes:
                     return string.Format("{0}mn", value);
-				case ReportFieldType.Int:
-					return string.Format("", value);
-				default:
-					if (value is Guid)
-						return string.Format("{0:N}", value);
-					return value.ToString();
-			}
-		}
-	}
+                case ReportFieldType.Int:
+                    return string.Format("", value);
+                default:
+                    if (value is Guid)
+                        return string.Format("{0:N}", value);
+                    return value.ToString();
+            }
+        }
+
+        /// <summary> Gets filtered report header metadata. </summary>
+        /// <param name="reportColumns"> The report columns. </param>
+        /// <param name="defaultReturnValue"> The default return value. </param>
+        /// <returns> The filtered report header metadata. </returns>
+        public static List<HeaderMetadata> GetFilteredReportHeaderMetadata(string reportColumns, Func<List<HeaderMetadata>> defaultReturnValue = null)
+        {
+            if (!string.IsNullOrEmpty(reportColumns))
+            {
+                var s = reportColumns.Split('|').Select(x => ReportHelper.GetHeaderMetadataType(x)).Where(x => x != HeaderMetadata.None);
+                return s.ToList();
+            }
+            else
+                if (defaultReturnValue != null)
+                    return defaultReturnValue();
+                else
+                    return new List<HeaderMetadata>();
+        }
+
+        /// <summary> Gets header metadata type. </summary>
+        /// <param name="header"> The header. </param>
+        /// <returns> The header metadata type. </returns>
+        public static HeaderMetadata GetHeaderMetadataType(string header)
+        {
+            if (string.IsNullOrEmpty(header))
+                return HeaderMetadata.None;
+
+            HeaderMetadata rType;
+
+            if (!Enum.TryParse<HeaderMetadata>(header, out rType))
+                return HeaderMetadata.None;
+
+            return rType;
+        }
+
+        /// <summary> Gets report view type. </summary>
+        /// <param name="rowType"> The type. </param>
+        /// <returns> The report view type. </returns>
+        public static ReportViewType GetReportViewType(string rowType)
+        {
+            if (string.IsNullOrEmpty(rowType))
+                return ReportViewType.ReportData;
+
+            ReportViewType rType;
+
+            if (!Enum.TryParse<ReportViewType>(rowType, out rType))
+                return ReportViewType.ReportData;
+
+            return rType;
+        }
+
+        /// <summary> Gets row type. </summary>
+        /// <param name="rowType"> The type. </param>
+        /// <returns> The row type. </returns>
+        public static ReportIncludeItemTypes GetRowType(string rowType)
+        {
+            if (string.IsNullOrEmpty(rowType))
+                return ReportIncludeItemTypes.BaseItem;
+
+            ReportIncludeItemTypes rType;
+
+            if (!Enum.TryParse<ReportIncludeItemTypes>(rowType, out rType))
+                return ReportIncludeItemTypes.BaseItem;
+
+            return rType;
+        }
+
+        /// <summary> Gets report display type. </summary>
+        /// <param name="displayType"> Type of the display. </param>
+        /// <returns> The report display type. </returns>
+        public static ReportDisplayType GetReportDisplayType(string displayType)
+        {
+            if (string.IsNullOrEmpty(displayType))
+                return ReportDisplayType.ScreenExport;
+
+            ReportDisplayType rType;
+
+            if (!Enum.TryParse<ReportDisplayType>(displayType, out rType))
+                return ReportDisplayType.ScreenExport;
+
+            return rType;
+        }
+
+        /// <summary> Gets core localized string. </summary>
+        /// <param name="phrase"> The phrase. </param>
+        /// <returns> The core localized string. </returns>
+        public static string GetCoreLocalizedString(string phrase)
+        {
+            return BaseItem.LocalizationManager.GetLocalizedString(phrase);
+        }
+
+        #endregion
+
+    }
 }

+ 25 - 0
MediaBrowser.Api/Reports/Common/ReportIncludeItemTypes.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+	public enum ReportIncludeItemTypes
+	{
+		MusicArtist,
+		MusicAlbum,
+		Book,
+		BoxSet,
+		Episode,
+		Game,
+		Video,
+		Movie,
+		MusicVideo,
+		Trailer,
+		Season,
+		Series,
+		Audio,
+		BaseItem,
+		Artist
+	}
+}

+ 4 - 15
MediaBrowser.Api/Reports/Common/ReportViewType.cs

@@ -6,20 +6,9 @@ namespace MediaBrowser.Api.Reports
 {
 	public enum ReportViewType
 	{
-		MusicArtist,
-		MusicAlbum,
-		Book,
-		BoxSet,
-		Episode,
-		Game,
-		Video,
-		Movie,
-		MusicVideo,
-		Trailer,
-		Season,
-		Series,
-		Audio,
-		BaseItem,
-		Artist
+        ReportData,
+        ReportStatistics,
+        ReportActivities
+
 	}
 }

+ 490 - 428
MediaBrowser.Api/Reports/Data/ReportBuilder.cs

@@ -17,164 +17,98 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Reports
 {
-	/// <summary> A report builder. </summary>
-	/// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
-	public class ReportBuilder : ReportBuilderBase
-	{
-
-		/// <summary>
-		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilder class. </summary>
-		/// <param name="libraryManager"> Manager for library. </param>
-		public ReportBuilder(ILibraryManager libraryManager)
-			: base(libraryManager)
-		{
-		}
-
-		private Func<bool, string> GetBoolString = s => s == true ? "x" : "";
-
-		public ReportResult GetReportResult(BaseItem[] items, ReportViewType reportRowType, BaseReportRequest request)
-		{
-			List<HeaderMetadata> headersMetadata = this.GetFilteredReportHeaderMetadata(reportRowType, request);
-
-			var headers = GetReportHeaders(reportRowType, headersMetadata);
-			var rows = GetReportRows(items, headersMetadata);
-
-			ReportResult result = new ReportResult { Headers = headers };
-			HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy);
-			int i = headers.FindIndex(x => x.FieldName == groupBy);
-			if (groupBy != HeaderMetadata.None && i > 0)
-			{
-				var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Genre = g.Trim(), Rows = x })
-					.GroupBy(x => x.Genre)
-					.OrderBy(x => x.Key)
-					.Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() });
-
-				result.Groups = rowsGroup.ToList();
-				result.IsGrouped = true;
-			}
-			else
-			{
-				result.Rows = rows;
-				result.IsGrouped = false;
-			}
-
-			return result;
-		}
-
-		public List<ReportHeader> GetReportHeaders(ReportViewType reportRowType, BaseReportRequest request)
-		{
-			List<ReportHeader> headersMetadata = this.GetReportHeaders(reportRowType);
-			if (request != null && !string.IsNullOrEmpty(request.ReportColumns))
-			{
-				List<HeaderMetadata> headersMetadataFiltered = this.GetFilteredReportHeaderMetadata(reportRowType, request);
-				foreach (ReportHeader reportHeader in headersMetadata)
-				{
-					if (!headersMetadataFiltered.Contains(reportHeader.FieldName))
-					{
-						reportHeader.Visible = false;
-					}
-				}
-
-
-			}
-
-			return headersMetadata;
-		}
-
-		public List<ReportHeader> GetReportHeaders(ReportViewType reportRowType, List<HeaderMetadata> headersMetadata = null)
-		{
-			if (headersMetadata == null)
-				headersMetadata = this.GetDefaultReportHeaderMetadata(reportRowType);
-
-			List<ReportOptions<BaseItem>> options = new List<ReportOptions<BaseItem>>();
-			foreach (HeaderMetadata header in headersMetadata)
-			{
-				options.Add(GetReportOption(header));
-			}
-
-
-			List<ReportHeader> headers = new List<ReportHeader>();
-			foreach (ReportOptions<BaseItem> option in options)
-			{
-				headers.Add(option.Header);
-			}
-			return headers;
-		}
-
-		private List<ReportRow> GetReportRows(IEnumerable<BaseItem> items, List<HeaderMetadata> headersMetadata)
-		{
-			List<ReportOptions<BaseItem>> options = new List<ReportOptions<BaseItem>>();
-			foreach (HeaderMetadata header in headersMetadata)
-			{
-				options.Add(GetReportOption(header));
-			}
-
-			var rows = new List<ReportRow>();
-
-			foreach (BaseItem item in items)
-			{
-				ReportRow rRow = GetRow(item);
-				foreach (ReportOptions<BaseItem> option in options)
-				{
-					object itemColumn = option.Column != null ? option.Column(item, rRow) : "";
-					object itemId = option.ItemID != null ? option.ItemID(item) : "";
-					ReportItem rItem = new ReportItem
-					{
-						Name = ReportHelper.ConvertToString(itemColumn, option.Header.HeaderFieldType),
-						Id = ReportHelper.ConvertToString(itemId, ReportFieldType.Object)
-					};
-					rRow.Columns.Add(rItem);
-				}
-
-				rows.Add(rRow);
-			}
-
-			return rows;
-		}
-
-		/// <summary> Gets a row. </summary>
-		/// <param name="item"> The item. </param>
-		/// <returns> The row. </returns>
-		private ReportRow GetRow(BaseItem item)
-		{
-			var hasTrailers = item as IHasTrailers;
-			var hasSpecialFeatures = item as IHasSpecialFeatures;
-			var video = item as Video;
-			ReportRow rRow = new ReportRow
-			{
-				Id = item.Id.ToString("N"),
-				HasLockData = item.IsLocked,
-				IsUnidentified = item.IsUnidentified,
-				HasLocalTrailer = hasTrailers != null ? hasTrailers.GetTrailerIds().Count() > 0 : false,
-				HasImageTagsPrimary = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Primary) > 0),
-				HasImageTagsBackdrop = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Backdrop) > 0),
-				HasImageTagsLogo = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Logo) > 0),
-				HasSpecials = hasSpecialFeatures != null ? hasSpecialFeatures.SpecialFeatureIds.Count > 0 : false,
-				HasSubtitles = video != null ? video.HasSubtitles : false,
-				RowType = ReportHelper.GetRowType(item.GetClientTypeName())
-			};
-			return rRow;
-		}
-		public List<HeaderMetadata> GetFilteredReportHeaderMetadata(ReportViewType reportRowType, BaseReportRequest request)
-		{
-			if (request != null && !string.IsNullOrEmpty(request.ReportColumns))
-			{
-				var s = request.ReportColumns.Split('|').Select(x => ReportHelper.GetHeaderMetadataType(x)).Where(x => x != HeaderMetadata.None);
-				return s.ToList();
-			}
-			else
-				return this.GetDefaultReportHeaderMetadata(reportRowType);
-
-		}
-
-		public List<HeaderMetadata> GetDefaultReportHeaderMetadata(ReportViewType reportRowType)
-		{
-			switch (reportRowType)
-			{
-				case ReportViewType.Season:
-					return new List<HeaderMetadata>
-					{
-						HeaderMetadata.StatusImage,
+    /// <summary> A report builder. </summary>
+    /// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
+    public class ReportBuilder : ReportBuilderBase
+    {
+
+        #region [Constructors]
+
+        /// <summary>
+        /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilder class. </summary>
+        /// <param name="libraryManager"> Manager for library. </param>
+        public ReportBuilder(ILibraryManager libraryManager)
+            : base(libraryManager)
+        {
+        }
+
+        #endregion
+
+        #region [Public Methods]
+
+        /// <summary> Gets report result. </summary>
+        /// <param name="items"> The items. </param>
+        /// <param name="request"> The request. </param>
+        /// <returns> The report result. </returns>
+        public ReportResult GetResult(BaseItem[] items, IReportsQuery request)
+        {
+            ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+            ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType);
+
+            List<ReportOptions<BaseItem>> options = this.GetReportOptions<BaseItem>(request,
+                () => this.GetDefaultHeaderMetadata(reportRowType),
+                (hm) => this.GetOption(hm)).Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType)).ToList();
+
+            var headers = GetHeaders<BaseItem>(options);
+            var rows = GetReportRows(items, options);
+
+            ReportResult result = new ReportResult { Headers = headers };
+            HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy);
+            int i = headers.FindIndex(x => x.FieldName == groupBy);
+            if (groupBy != HeaderMetadata.None && i >= 0)
+            {
+                var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Group = g.Trim(), Rows = x })
+                    .GroupBy(x => x.Group)
+                    .OrderBy(x => x.Key)
+                    .Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() });
+
+                result.Groups = rowsGroup.ToList();
+                result.IsGrouped = true;
+            }
+            else
+            {
+                result.Rows = rows;
+                result.IsGrouped = false;
+            }
+
+            return result;
+        }
+
+        #endregion
+
+        #region [Protected Internal Methods]
+
+        /// <summary> Gets the headers. </summary>
+        /// <typeparam name="H"> Type of the header. </typeparam>
+        /// <param name="request"> The request. </param>
+        /// <returns> The headers. </returns>
+        /// <seealso cref="M:MediaBrowser.Api.Reports.ReportBuilderBase.GetHeaders{H}(H)"/>
+        protected internal override List<ReportHeader> GetHeaders<H>(H request)
+        {
+            ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+            return this.GetHeaders<BaseItem>(request, () => this.GetDefaultHeaderMetadata(reportRowType), (hm) => this.GetOption(hm));
+        }
+
+        #endregion
+
+        #region [Private Methods]
+
+        /// <summary> Gets default report header metadata. </summary>
+        /// <param name="reportIncludeItemTypes"> Type of the report row. </param>
+        /// <returns> The default report header metadata. </returns>
+        private List<HeaderMetadata> GetDefaultHeaderMetadata(ReportIncludeItemTypes reportIncludeItemTypes)
+        {
+            switch (reportIncludeItemTypes)
+            {
+                case ReportIncludeItemTypes.Season:
+                    return new List<HeaderMetadata>
+					{   
+                        HeaderMetadata.Status,                     
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Series,
 						HeaderMetadata.Season,
 						HeaderMetadata.SeasonNumber,
@@ -183,10 +117,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Genres
 					};
 
-				case ReportViewType.Series:
-					return new List<HeaderMetadata>
-					{
-						HeaderMetadata.StatusImage,
+                case ReportIncludeItemTypes.Series:
+                    return new List<HeaderMetadata>
+					{     
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.Network,
 						HeaderMetadata.DateAdded,
@@ -199,10 +138,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Specials
 					};
 
-				case ReportViewType.MusicAlbum:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.MusicAlbum:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.AlbumArtist,
 						HeaderMetadata.DateAdded,
@@ -212,10 +156,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Genres
 					};
 
-				case ReportViewType.MusicArtist:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.MusicArtist:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.MusicArtist,
 						HeaderMetadata.Countries,
 						HeaderMetadata.DateAdded,
@@ -223,10 +172,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Genres
 					};
 
-				case ReportViewType.Game:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.Game:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.GameSystem,
 						HeaderMetadata.DateAdded,
@@ -239,10 +193,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Trailers
 					};
 
-				case ReportViewType.Movie:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.Movie:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.DateAdded,
 						HeaderMetadata.ReleaseDate,
@@ -259,10 +218,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Specials
 					};
 
-				case ReportViewType.Book:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.Book:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.DateAdded,
 						HeaderMetadata.ReleaseDate,
@@ -272,10 +236,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.CommunityRating
 					};
 
-				case ReportViewType.BoxSet:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.BoxSet:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.DateAdded,
 						HeaderMetadata.ReleaseDate,
@@ -286,10 +255,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Trailers
 					};
 
-				case ReportViewType.Audio:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.Audio:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.AudioAlbumArtist,
 						HeaderMetadata.AudioAlbum,
@@ -305,10 +279,15 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Audio
 					};
 
-				case ReportViewType.Episode:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.Episode:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.EpisodeSeries,
 						HeaderMetadata.Season,
@@ -327,14 +306,23 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Specials
 					};
 
-				case ReportViewType.Video:
-				case ReportViewType.MusicVideo:
-				case ReportViewType.Trailer:
-				case ReportViewType.BaseItem:
-				default:
-					return new List<HeaderMetadata>
+                case ReportIncludeItemTypes.Video:
+                case ReportIncludeItemTypes.MusicVideo:
+                case ReportIncludeItemTypes.Trailer:
+                case ReportIncludeItemTypes.BaseItem:
+                default:
+                    return new List<HeaderMetadata>
 					{
-						HeaderMetadata.StatusImage,
+                        HeaderMetadata.Status,
+                        HeaderMetadata.Locked,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
+						HeaderMetadata.Unidentified,
+                        HeaderMetadata.ImagePrimary,
+                        HeaderMetadata.ImageBackdrop,
+                        HeaderMetadata.ImageLogo,
 						HeaderMetadata.Name,
 						HeaderMetadata.DateAdded,
 						HeaderMetadata.ReleaseDate,
@@ -351,239 +339,313 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Specials
 					};
 
-			}
-
-		}
-
-		/// <summary> Gets report option. </summary>
-		/// <param name="header"> The header. </param>
-		/// <param name="sortField"> The sort field. </param>
-		/// <returns> The report option. </returns>
-		private ReportOptions<BaseItem> GetReportOption(HeaderMetadata header, string sortField = "")
-		{
-			ReportHeader reportHeader = new ReportHeader
-			{
-				HeaderFieldType = ReportFieldType.String,
-				SortField = sortField,
-				Type = "",
-				ItemViewType = ItemViewType.None
-			};
-
-			Func<BaseItem, ReportRow, object> column = null;
-			Func<BaseItem, object> itemId = null;
-			HeaderMetadata internalHeader = header;
-
-			switch (header)
-			{
-				case HeaderMetadata.StatusImage:
-					reportHeader.ItemViewType = ItemViewType.StatusImage;
-					internalHeader = HeaderMetadata.Status;
-					reportHeader.CanGroup = false;
-					break;
-
-				case HeaderMetadata.Name:
-					column = (i, r) => i.Name;
-					reportHeader.ItemViewType = ItemViewType.Detail;
-					reportHeader.SortField = "SortName";
-					break;
-
-				case HeaderMetadata.DateAdded:
-					column = (i, r) => i.DateCreated;
-					reportHeader.SortField = "DateCreated,SortName";
-					reportHeader.HeaderFieldType = ReportFieldType.DateTime;
-					reportHeader.Type = "";
-					break;
-
-				case HeaderMetadata.PremiereDate:
-				case HeaderMetadata.ReleaseDate:
-					column = (i, r) => i.PremiereDate;
-					reportHeader.HeaderFieldType = ReportFieldType.DateTime;
-					reportHeader.SortField = "ProductionYear,PremiereDate,SortName";
-					break;
-
-				case HeaderMetadata.Runtime:
-					column = (i, r) => this.GetRuntimeDateTime(i.RunTimeTicks);
-					reportHeader.HeaderFieldType = ReportFieldType.Minutes;
-					reportHeader.SortField = "Runtime,SortName";
-					break;
-
-				case HeaderMetadata.PlayCount:
-					reportHeader.HeaderFieldType = ReportFieldType.Int;
-					break;
-
-				case HeaderMetadata.Season:
-					column = (i, r) => this.GetEpisode(i);
-					reportHeader.ItemViewType = ItemViewType.Detail;
-					reportHeader.SortField = "SortName";
-					break;
-
-				case HeaderMetadata.SeasonNumber:
-					column = (i, r) => this.GetObject<Season, string>(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString());
-					reportHeader.SortField = "IndexNumber";
-					reportHeader.HeaderFieldType = ReportFieldType.Int;
-					break;
-
-				case HeaderMetadata.Series:
-					column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
-					reportHeader.ItemViewType = ItemViewType.Detail;
-					reportHeader.SortField = "SeriesSortName,SortName";
-					break;
-
-				case HeaderMetadata.EpisodeSeries:
-					column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
-					reportHeader.ItemViewType = ItemViewType.Detail;
-					itemId = (i) =>
-					{
-						Series series = this.GetObject<Episode, Series>(i, (x) => x.Series);
-						if (series == null)
-							return string.Empty;
-						return series.Id;
-					};
-					reportHeader.SortField = "SeriesSortName,SortName";
-					internalHeader = HeaderMetadata.Series;
-					break;
-
-				case HeaderMetadata.EpisodeSeason:
-					column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
-					reportHeader.ItemViewType = ItemViewType.Detail;
-					itemId = (i) =>
-					{
-						Season season = this.GetObject<Episode, Season>(i, (x) => x.Season);
-						if (season == null)
-							return string.Empty;
-						return season.Id;
-					};
-					reportHeader.SortField = "SortName";
-					internalHeader = HeaderMetadata.Season;
-					break;
-
-				case HeaderMetadata.Network:
-					column = (i, r) => this.GetListAsString(i.Studios);
-					itemId = (i) => this.GetStudioID(i.Studios.FirstOrDefault());
-					reportHeader.ItemViewType = ItemViewType.ItemByNameDetails;
-					reportHeader.SortField = "Studio,SortName";
-					break;
-
-				case HeaderMetadata.Year:
-					column = (i, r) => this.GetSeriesProductionYear(i);
-					reportHeader.SortField = "ProductionYear,PremiereDate,SortName";
-					break;
-
-				case HeaderMetadata.ParentalRating:
-					column = (i, r) => i.OfficialRating;
-					reportHeader.SortField = "OfficialRating,SortName";
-					break;
-
-				case HeaderMetadata.CommunityRating:
-					column = (i, r) => i.CommunityRating;
-					reportHeader.SortField = "CommunityRating,SortName";
-					break;
-
-				case HeaderMetadata.Trailers:
-					column = (i, r) => this.GetBoolString(r.HasLocalTrailer);
-					reportHeader.ItemViewType = ItemViewType.TrailersImage;
-					break;
-
-				case HeaderMetadata.Specials:
-					column = (i, r) => this.GetBoolString(r.HasSpecials);
-					reportHeader.ItemViewType = ItemViewType.SpecialsImage;
-					break;
-
-				case HeaderMetadata.GameSystem:
-					column = (i, r) => this.GetObject<Game, string>(i, (x) => x.GameSystem);
-					reportHeader.SortField = "GameSystem,SortName";
-					break;
-
-				case HeaderMetadata.Players:
-					column = (i, r) => this.GetObject<Game, int?>(i, (x) => x.PlayersSupported);
-					reportHeader.SortField = "Players,GameSystem,SortName";
-					break;
-
-				case HeaderMetadata.AlbumArtist:
-					column = (i, r) => this.GetObject<MusicAlbum, string>(i, (x) => x.AlbumArtist);
-					itemId = (i) => this.GetPersonID(this.GetObject<MusicAlbum, string>(i, (x) => x.AlbumArtist));
-					reportHeader.ItemViewType = ItemViewType.Detail;
-					reportHeader.SortField = "AlbumArtist,Album,SortName";
-
-					break;
-				case HeaderMetadata.MusicArtist:
-					column = (i, r) => this.GetObject<MusicArtist, string>(i, (x) => x.GetLookupInfo().Name);
-					reportHeader.ItemViewType = ItemViewType.Detail;
-					reportHeader.SortField = "AlbumArtist,Album,SortName";
-					internalHeader = HeaderMetadata.AlbumArtist;
-					break;
-				case HeaderMetadata.AudioAlbumArtist:
-					column = (i, r) => this.GetListAsString(this.GetObject<Audio, List<string>>(i, (x) => x.AlbumArtists));
-					reportHeader.SortField = "AlbumArtist,Album,SortName";
-					internalHeader = HeaderMetadata.AlbumArtist;
-					break;
-
-				case HeaderMetadata.AudioAlbum:
-					column = (i, r) => this.GetObject<Audio, string>(i, (x) => x.Album);
-					reportHeader.SortField = "Album,SortName";
-					internalHeader = HeaderMetadata.Album;
-					break;
-
-				case HeaderMetadata.Countries:
-					column = (i, r) => this.GetListAsString(this.GetObject<IHasProductionLocations, List<string>>(i, (x) => x.ProductionLocations));
-					break;
-
-				case HeaderMetadata.Disc:
-					column = (i, r) => i.ParentIndexNumber;
-					break;
-
-				case HeaderMetadata.Track:
-					column = (i, r) => i.IndexNumber;
-					break;
-
-				case HeaderMetadata.Tracks:
-					column = (i, r) => this.GetObject<MusicAlbum, List<Audio>>(i, (x) => x.Tracks.ToList(), new List<Audio>()).Count();
-					break;
-
-				case HeaderMetadata.Audio:
-					column = (i, r) => this.GetAudioStream(i);
-					break;
-
-				case HeaderMetadata.EmbeddedImage:
-					break;
-
-				case HeaderMetadata.Video:
-					column = (i, r) => this.GetVideoStream(i);
-					break;
-
-				case HeaderMetadata.Resolution:
-					column = (i, r) => this.GetVideoResolution(i);
-					break;
-
-				case HeaderMetadata.Subtitles:
-					column = (i, r) => this.GetBoolString(r.HasSubtitles);
-					reportHeader.ItemViewType = ItemViewType.SubtitleImage;
-					break;
-
-				case HeaderMetadata.Genres:
-					column = (i, r) => this.GetListAsString(i.Genres);
-					break;
-
-			}
-
-			string headerName = "";
-			if (internalHeader != HeaderMetadata.None)
-			{
-				string localHeader = "Header" + internalHeader.ToString();
-				headerName = internalHeader != HeaderMetadata.None ? ReportHelper.GetJavaScriptLocalizedString(localHeader) : "";
-				if (string.Compare(localHeader, headerName, StringComparison.CurrentCultureIgnoreCase) == 0)
-					headerName = ReportHelper.GetServerLocalizedString(localHeader);
-			}
-
-			reportHeader.Name = headerName;
-			reportHeader.FieldName = header;
-			ReportOptions<BaseItem> option = new ReportOptions<BaseItem>()
-			{
-				Header = reportHeader,
-				Column = column,
-				ItemID = itemId
-			};
-			return option;
-		}
-	}
+            }
+
+        }
+
+        /// <summary> Gets report option. </summary>
+        /// <param name="header"> The header. </param>
+        /// <param name="sortField"> The sort field. </param>
+        /// <returns> The report option. </returns>
+        private ReportOptions<BaseItem> GetOption(HeaderMetadata header, string sortField = "")
+        {
+            HeaderMetadata internalHeader = header;
+
+            ReportOptions<BaseItem> option = new ReportOptions<BaseItem>()
+            {
+                Header = new ReportHeader
+                {
+                    HeaderFieldType = ReportFieldType.String,
+                    SortField = sortField,
+                    Type = "",
+                    ItemViewType = ItemViewType.None
+                }
+            };
+
+            switch (header)
+            {
+                case HeaderMetadata.Status:
+                    option.Header.ItemViewType = ItemViewType.StatusImage;
+                    internalHeader = HeaderMetadata.Status;
+                    option.Header.CanGroup = false;
+                    option.Header.DisplayType = ReportDisplayType.Screen;
+                    break;
+                case HeaderMetadata.Locked:
+                    option.Column = (i, r) => this.GetBoolString(r.HasLockData);
+                    option.Header.ItemViewType = ItemViewType.LockDataImage;
+                    option.Header.CanGroup = false;
+                    option.Header.DisplayType = ReportDisplayType.Export;
+                    break;
+                case HeaderMetadata.Unidentified:
+                    option.Column = (i, r) => this.GetBoolString(r.IsUnidentified);
+                    option.Header.ItemViewType = ItemViewType.UnidentifiedImage;
+                    option.Header.CanGroup = false;
+                    option.Header.DisplayType = ReportDisplayType.Export;
+                    break;
+                case HeaderMetadata.ImagePrimary:
+                    option.Column = (i, r) => this.GetBoolString(r.HasImageTagsPrimary);
+                    option.Header.ItemViewType = ItemViewType.TagsPrimaryImage;
+                    option.Header.CanGroup = false;
+                    option.Header.DisplayType = ReportDisplayType.Export;
+                    break;
+                case HeaderMetadata.ImageBackdrop:
+                    option.Column = (i, r) => this.GetBoolString(r.HasImageTagsBackdrop);
+                    option.Header.ItemViewType = ItemViewType.TagsBackdropImage;
+                    option.Header.CanGroup = false;
+                    option.Header.DisplayType = ReportDisplayType.Export;
+                    break;
+                case HeaderMetadata.ImageLogo:
+                    option.Column = (i, r) => this.GetBoolString(r.HasImageTagsLogo);
+                    option.Header.ItemViewType = ItemViewType.TagsLogoImage;
+                    option.Header.CanGroup = false;
+                    option.Header.DisplayType = ReportDisplayType.Export;
+                    break;
+
+                case HeaderMetadata.Name:
+                    option.Column = (i, r) => i.Name;
+                    option.Header.ItemViewType = ItemViewType.Detail;
+                    option.Header.SortField = "SortName";
+                    break;
+
+                case HeaderMetadata.DateAdded:
+                    option.Column = (i, r) => i.DateCreated;
+                    option.Header.SortField = "DateCreated,SortName";
+                    option.Header.HeaderFieldType = ReportFieldType.DateTime;
+                    option.Header.Type = "";
+                    break;
+
+                case HeaderMetadata.PremiereDate:
+                case HeaderMetadata.ReleaseDate:
+                    option.Column = (i, r) => i.PremiereDate;
+                    option.Header.HeaderFieldType = ReportFieldType.DateTime;
+                    option.Header.SortField = "ProductionYear,PremiereDate,SortName";
+                    break;
+
+                case HeaderMetadata.Runtime:
+                    option.Column = (i, r) => this.GetRuntimeDateTime(i.RunTimeTicks);
+                    option.Header.HeaderFieldType = ReportFieldType.Minutes;
+                    option.Header.SortField = "Runtime,SortName";
+                    break;
+
+                case HeaderMetadata.PlayCount:
+                    option.Header.HeaderFieldType = ReportFieldType.Int;
+                    break;
+
+                case HeaderMetadata.Season:
+                    option.Column = (i, r) => this.GetEpisode(i);
+                    option.Header.ItemViewType = ItemViewType.Detail;
+                    option.Header.SortField = "SortName";
+                    break;
+
+                case HeaderMetadata.SeasonNumber:
+                    option.Column = (i, r) => this.GetObject<Season, string>(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString());
+                    option.Header.SortField = "IndexNumber";
+                    option.Header.HeaderFieldType = ReportFieldType.Int;
+                    break;
+
+                case HeaderMetadata.Series:
+                    option.Column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
+                    option.Header.ItemViewType = ItemViewType.Detail;
+                    option.Header.SortField = "SeriesSortName,SortName";
+                    break;
+
+                case HeaderMetadata.EpisodeSeries:
+                    option.Column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
+                    option.Header.ItemViewType = ItemViewType.Detail;
+                    option.ItemID = (i) =>
+                    {
+                        Series series = this.GetObject<Episode, Series>(i, (x) => x.Series);
+                        if (series == null)
+                            return string.Empty;
+                        return series.Id;
+                    };
+                    option.Header.SortField = "SeriesSortName,SortName";
+                    internalHeader = HeaderMetadata.Series;
+                    break;
+
+                case HeaderMetadata.EpisodeSeason:
+                    option.Column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
+                    option.Header.ItemViewType = ItemViewType.Detail;
+                    option.ItemID = (i) =>
+                    {
+                        Season season = this.GetObject<Episode, Season>(i, (x) => x.Season);
+                        if (season == null)
+                            return string.Empty;
+                        return season.Id;
+                    };
+                    option.Header.SortField = "SortName";
+                    internalHeader = HeaderMetadata.Season;
+                    break;
+
+                case HeaderMetadata.Network:
+                    option.Column = (i, r) => this.GetListAsString(i.Studios);
+                    option.ItemID = (i) => this.GetStudioID(i.Studios.FirstOrDefault());
+                    option.Header.ItemViewType = ItemViewType.ItemByNameDetails;
+                    option.Header.SortField = "Studio,SortName";
+                    break;
+
+                case HeaderMetadata.Year:
+                    option.Column = (i, r) => this.GetSeriesProductionYear(i);
+                    option.Header.SortField = "ProductionYear,PremiereDate,SortName";
+                    break;
+
+                case HeaderMetadata.ParentalRating:
+                    option.Column = (i, r) => i.OfficialRating;
+                    option.Header.SortField = "OfficialRating,SortName";
+                    break;
+
+                case HeaderMetadata.CommunityRating:
+                    option.Column = (i, r) => i.CommunityRating;
+                    option.Header.SortField = "CommunityRating,SortName";
+                    break;
+
+                case HeaderMetadata.Trailers:
+                    option.Column = (i, r) => this.GetBoolString(r.HasLocalTrailer);
+                    option.Header.ItemViewType = ItemViewType.TrailersImage;
+                    break;
+
+                case HeaderMetadata.Specials:
+                    option.Column = (i, r) => this.GetBoolString(r.HasSpecials);
+                    option.Header.ItemViewType = ItemViewType.SpecialsImage;
+                    break;
+
+                case HeaderMetadata.GameSystem:
+                    option.Column = (i, r) => this.GetObject<Game, string>(i, (x) => x.GameSystem);
+                    option.Header.SortField = "GameSystem,SortName";
+                    break;
+
+                case HeaderMetadata.Players:
+                    option.Column = (i, r) => this.GetObject<Game, int?>(i, (x) => x.PlayersSupported);
+                    option.Header.SortField = "Players,GameSystem,SortName";
+                    break;
+
+                case HeaderMetadata.AlbumArtist:
+                    option.Column = (i, r) => this.GetObject<MusicAlbum, string>(i, (x) => x.AlbumArtist);
+                    option.ItemID = (i) => this.GetPersonID(this.GetObject<MusicAlbum, string>(i, (x) => x.AlbumArtist));
+                    option.Header.ItemViewType = ItemViewType.Detail;
+                    option.Header.SortField = "AlbumArtist,Album,SortName";
+
+                    break;
+                case HeaderMetadata.MusicArtist:
+                    option.Column = (i, r) => this.GetObject<MusicArtist, string>(i, (x) => x.GetLookupInfo().Name);
+                    option.Header.ItemViewType = ItemViewType.Detail;
+                    option.Header.SortField = "AlbumArtist,Album,SortName";
+                    internalHeader = HeaderMetadata.AlbumArtist;
+                    break;
+                case HeaderMetadata.AudioAlbumArtist:
+                    option.Column = (i, r) => this.GetListAsString(this.GetObject<Audio, List<string>>(i, (x) => x.AlbumArtists));
+                    option.Header.SortField = "AlbumArtist,Album,SortName";
+                    internalHeader = HeaderMetadata.AlbumArtist;
+                    break;
+
+                case HeaderMetadata.AudioAlbum:
+                    option.Column = (i, r) => this.GetObject<Audio, string>(i, (x) => x.Album);
+                    option.Header.SortField = "Album,SortName";
+                    internalHeader = HeaderMetadata.Album;
+                    break;
+
+                case HeaderMetadata.Countries:
+                    option.Column = (i, r) => this.GetListAsString(this.GetObject<IHasProductionLocations, List<string>>(i, (x) => x.ProductionLocations));
+                    break;
+
+                case HeaderMetadata.Disc:
+                    option.Column = (i, r) => i.ParentIndexNumber;
+                    break;
+
+                case HeaderMetadata.Track:
+                    option.Column = (i, r) => i.IndexNumber;
+                    break;
+
+                case HeaderMetadata.Tracks:
+                    option.Column = (i, r) => this.GetObject<MusicAlbum, List<Audio>>(i, (x) => x.Tracks.ToList(), new List<Audio>()).Count();
+                    break;
+
+                case HeaderMetadata.Audio:
+                    option.Column = (i, r) => this.GetAudioStream(i);
+                    break;
+
+                case HeaderMetadata.EmbeddedImage:
+                    break;
+
+                case HeaderMetadata.Video:
+                    option.Column = (i, r) => this.GetVideoStream(i);
+                    break;
+
+                case HeaderMetadata.Resolution:
+                    option.Column = (i, r) => this.GetVideoResolution(i);
+                    break;
+
+                case HeaderMetadata.Subtitles:
+                    option.Column = (i, r) => this.GetBoolString(r.HasSubtitles);
+                    option.Header.ItemViewType = ItemViewType.SubtitleImage;
+                    break;
+
+                case HeaderMetadata.Genres:
+                    option.Column = (i, r) => this.GetListAsString(i.Genres);
+                    break;
+
+            }
+
+            option.Header.Name = GetLocalizedHeader(internalHeader);
+            option.Header.FieldName = header;
+
+            return option;
+        }
+
+        /// <summary> Gets report rows. </summary>
+        /// <param name="items"> The items. </param>
+        /// <param name="options"> Options for controlling the operation. </param>
+        /// <returns> The report rows. </returns>
+        private List<ReportRow> GetReportRows(IEnumerable<BaseItem> items, List<ReportOptions<BaseItem>> options)
+        {
+            var rows = new List<ReportRow>();
+
+            foreach (BaseItem item in items)
+            {
+                ReportRow rRow = GetRow(item);
+                foreach (ReportOptions<BaseItem> option in options)
+                {
+                    object itemColumn = option.Column != null ? option.Column(item, rRow) : "";
+                    object itemId = option.ItemID != null ? option.ItemID(item) : "";
+                    ReportItem rItem = new ReportItem
+                    {
+                        Name = ReportHelper.ConvertToString(itemColumn, option.Header.HeaderFieldType),
+                        Id = ReportHelper.ConvertToString(itemId, ReportFieldType.Object)
+                    };
+                    rRow.Columns.Add(rItem);
+                }
+
+                rows.Add(rRow);
+            }
+
+            return rows;
+        }
+
+        /// <summary> Gets a row. </summary>
+        /// <param name="item"> The item. </param>
+        /// <returns> The row. </returns>
+        private ReportRow GetRow(BaseItem item)
+        {
+            var hasTrailers = item as IHasTrailers;
+            var hasSpecialFeatures = item as IHasSpecialFeatures;
+            var video = item as Video;
+            ReportRow rRow = new ReportRow
+            {
+                Id = item.Id.ToString("N"),
+                HasLockData = item.IsLocked,
+                IsUnidentified = item.IsUnidentified,
+                HasLocalTrailer = hasTrailers != null ? hasTrailers.GetTrailerIds().Count() > 0 : false,
+                HasImageTagsPrimary = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Primary) > 0),
+                HasImageTagsBackdrop = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Backdrop) > 0),
+                HasImageTagsLogo = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Logo) > 0),
+                HasSpecials = hasSpecialFeatures != null ? hasSpecialFeatures.SpecialFeatureIds.Count > 0 : false,
+                HasSubtitles = video != null ? video.HasSubtitles : false,
+                RowType = ReportHelper.GetRowType(item.GetClientTypeName())
+            };
+            return rRow;
+        }
+
+        #endregion
+
+    }
 }

+ 2 - 1
MediaBrowser.Api/Reports/Data/ReportOptions.cs

@@ -7,8 +7,9 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Reports
 {
+
 	/// <summary> A report options. </summary>
-	internal class ReportOptions<I>
+	public class ReportOptions<I>
 	{
 		/// <summary> Initializes a new instance of the ReportOptions class. </summary>
 		public ReportOptions()

+ 0 - 0
MediaBrowser.Api/Reports/Data/ReportGroup.cs → MediaBrowser.Api/Reports/Model/ReportGroup.cs


+ 10 - 0
MediaBrowser.Api/Reports/Data/ReportHeader.cs → MediaBrowser.Api/Reports/Model/ReportHeader.cs

@@ -16,6 +16,8 @@ namespace MediaBrowser.Api.Reports
 			ItemViewType = ItemViewType.None;
 			Visible = true;
 			CanGroup = true;
+            ShowHeaderLabel = true;
+            DisplayType = ReportDisplayType.ScreenExport;
 		}
 
 		/// <summary> Gets or sets the type of the header field. </summary>
@@ -46,6 +48,14 @@ namespace MediaBrowser.Api.Reports
 		/// <value> true if visible, false if not. </value>
 		public bool Visible { get; set; }
 
+        /// <summary> Gets or sets the type of the display. </summary>
+        /// <value> The type of the display. </value>
+        public ReportDisplayType DisplayType { get; set; }
+
+        /// <summary> Gets or sets a value indicating whether the header label is shown. </summary>
+        /// <value> true if show header label, false if not. </value>
+        public bool ShowHeaderLabel { get; set; }
+
 		/// <summary> Gets or sets a value indicating whether we can group. </summary>
 		/// <value> true if we can group, false if not. </value>
 		public bool CanGroup { get; set; }

+ 0 - 0
MediaBrowser.Api/Reports/Data/ReportItem.cs → MediaBrowser.Api/Reports/Model/ReportItem.cs


+ 0 - 0
MediaBrowser.Api/Reports/Data/ReportResult.cs → MediaBrowser.Api/Reports/Model/ReportResult.cs


+ 5 - 1
MediaBrowser.Api/Reports/Data/ReportRow.cs → MediaBrowser.Api/Reports/Model/ReportRow.cs

@@ -66,6 +66,10 @@ namespace MediaBrowser.Api.Reports
 
 		/// <summary> Gets or sets the type. </summary>
 		/// <value> The type. </value>
-		public ReportViewType RowType { get; set; }
+		public ReportIncludeItemTypes RowType { get; set; }
+
+        /// <summary> Gets or sets the identifier of the user. </summary>
+        /// <value> The identifier of the user. </value>
+        public string UserId { get; set; }
 	}
 }

+ 147 - 229
MediaBrowser.Api/Reports/ReportRequests.cs

@@ -7,251 +7,90 @@ using System.Linq;
 
 namespace MediaBrowser.Api.Reports
 {
-    public class BaseReportRequest : BaseItemsRequest
-	{
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string UserId { get; set; }
-
-        /// <summary>
-        /// Limit results to items containing a specific person
-        /// </summary>
-        /// <value>The person.</value>
-        [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string Person { get; set; }
-
-        [ApiMember(Name = "PersonIds", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string PersonIds { get; set; }
-
-        /// <summary>
-        /// If the Person filter is used, this can also be used to restrict to a specific person type
-        /// </summary>
-        /// <value>The type of the person.</value>
-        [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string PersonTypes { get; set; }
-
-        /// <summary>
-        /// Limit results to items containing specific studios
-        /// </summary>
-        /// <value>The studios.</value>
-        [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Studios { get; set; }
-
-        [ApiMember(Name = "StudioIds", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string StudioIds { get; set; }
-
-        /// <summary>
-        /// Gets or sets the studios.
-        /// </summary>
-        /// <value>The studios.</value>
-        [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Artists { get; set; }
-
-        [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string ArtistIds { get; set; }
-
-        [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Albums { get; set; }
+    public interface IReportsDownload : IReportsQuery
+    {
+        /// <summary> Gets or sets the minimum date. </summary>
+        /// <value> The minimum date. </value>
+        string MinDate { get; set; }
+    }
 
+    /// <summary> Interface for reports query. </summary>
+    public interface IReportsQuery : IReportsHeader
+    {
         /// <summary>
-        /// Gets or sets the item ids.
-        /// </summary>
-        /// <value>The item ids.</value>
-        [ApiMember(Name = "Ids", Description = "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Ids { get; set; }
-        
-        public bool HasQueryLimit { get; set; }
-		public string GroupBy { get; set; }
-
-		public string ReportColumns { get; set; }
+        /// Gets or sets a value indicating whether this MediaBrowser.Api.Reports.GetActivityLogs has
+        /// query limit. </summary>
+        /// <value>
+        /// true if this MediaBrowser.Api.Reports.GetActivityLogs has query limit, false if not. </value>
+        bool HasQueryLimit { get; set; }
+        /// <summary> Gets or sets who group this MediaBrowser.Api.Reports.GetActivityLogs. </summary>
+        /// <value> Describes who group this MediaBrowser.Api.Reports.GetActivityLogs. </value>
+        string GroupBy { get; set; }
 
         /// <summary>
-        /// Gets or sets the video types.
+        /// Skips over a given number of items within the results. Use for paging.
         /// </summary>
-        /// <value>The video types.</value>
-        [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string VideoTypes { get; set; }
-
+        /// <value>The start index.</value>
+        int? StartIndex { get; set; }
         /// <summary>
-        /// Gets or sets the video formats.
+        /// The maximum number of items to return
         /// </summary>
-        /// <value>The video formats.</value>
-        [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? Is3D { get; set; }
+        /// <value>The limit.</value>
+        int? Limit { get; set; }
 
-        /// <summary>
-        /// Gets or sets the series status.
-        /// </summary>
-        /// <value>The series status.</value>
-        [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string SeriesStatus { get; set; }
+    }
+    public interface IReportsHeader
+    {
+        /// <summary> Gets or sets the report view. </summary>
+        /// <value> The report view. </value>
+        string ReportView { get; set; }
 
-        [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameStartsWithOrGreater { get; set; }
+        /// <summary> Gets or sets the report columns. </summary>
+        /// <value> The report columns. </value>
+        string ReportColumns { get; set; }
 
-        [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameStartsWith { get; set; }
+        /// <summary> Gets or sets a list of types of the include items. </summary>
+        /// <value> A list of types of the include items. </value>
+        string IncludeItemTypes { get; set; }
 
-        [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameLessThan { get; set; }
+        /// <summary> Gets or sets a list of types of the displays. </summary>
+        /// <value> A list of types of the displays. </value>
+        string DisplayType { get; set; }
 
-        [ApiMember(Name = "AlbumArtistStartsWithOrGreater", Description = "Optional filter by items whose album artist is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string AlbumArtistStartsWithOrGreater { get; set; }
+    }
 
-        /// <summary>
-        /// Gets or sets the air days.
-        /// </summary>
-        /// <value>The air days.</value>
-        [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string AirDays { get; set; }
+    public class BaseReportRequest : BaseItemsRequest, IReportsQuery
+    {
+        /// <summary> Gets or sets the report view. </summary>
+        /// <value> The report view. </value>
+        [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportStatistics, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ReportView { get; set; }
+        
+        /// <summary> Gets or sets the report view. </summary>
+        /// <value> The report view. </value>
+        [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string DisplayType { get; set; }
 
         /// <summary>
-        /// Gets or sets the min offical rating.
-        /// </summary>
-        /// <value>The min offical rating.</value>
-        [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string MinOfficialRating { get; set; }
+        /// Gets or sets a value indicating whether this MediaBrowser.Api.Reports.BaseReportRequest has
+        /// query limit. </summary>
+        /// <value>
+        /// true if this MediaBrowser.Api.Reports.BaseReportRequest has query limit, false if not. </value>
+        [ApiMember(Name = "HasQueryLimit", Description = "Optional. If specified, results will include all records.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool HasQueryLimit { get; set; }
 
         /// <summary>
-        /// Gets or sets the max offical rating.
-        /// </summary>
-        /// <value>The max offical rating.</value>
-        [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string MaxOfficialRating { get; set; }
-
-        [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasThemeSong { get; set; }
-
-        [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasThemeVideo { get; set; }
-
-        [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasSubtitles { get; set; }
-
-        [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasSpecialFeature { get; set; }
-
-        [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasTrailer { get; set; }
-
-        [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string AdjacentTo { get; set; }
-
-        [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? MinIndexNumber { get; set; }
-
-        [ApiMember(Name = "MinPlayers", Description = "Optional filter by minimum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? MinPlayers { get; set; }
-
-        [ApiMember(Name = "MaxPlayers", Description = "Optional filter by maximum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? MaxPlayers { get; set; }
-
-        [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? ParentIndexNumber { get; set; }
-
-        [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasParentalRating { get; set; }
+        /// Gets or sets who group this MediaBrowser.Api.Reports.BaseReportRequest. </summary>
+        /// <value> Describes who group this MediaBrowser.Api.Reports.BaseReportRequest. </value>
+        [ApiMember(Name = "GroupBy", Description = "Optional. If specified, results will include grouped records.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string GroupBy { get; set; }
 
-        [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsHD { get; set; }
+        /// <summary> Gets or sets the report columns. </summary>
+        /// <value> The report columns. </value>
+        [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ReportColumns { get; set; }
 
-        [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string LocationTypes { get; set; }
-
-        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string ExcludeLocationTypes { get; set; }
-
-        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsMissing { get; set; }
-
-        [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsUnaired { get; set; }
-
-        [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsVirtualUnaired { get; set; }
-
-        [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public double? MinCommunityRating { get; set; }
-
-        [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public double? MinCriticRating { get; set; }
-
-        [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? AiredDuringSeason { get; set; }
-
-        [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string MinPremiereDate { get; set; }
-
-        [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string MaxPremiereDate { get; set; }
-
-        [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasOverview { get; set; }
-
-        [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasImdbId { get; set; }
-
-        [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasTmdbId { get; set; }
-
-        [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasTvdbId { get; set; }
-
-        [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsYearMismatched { get; set; }
-
-        [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsInBoxSet { get; set; }
-
-        [ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? IsLocked { get; set; }
-
-        [ApiMember(Name = "IsUnidentified", Description = "Optional filter by items that are unidentified by internet metadata providers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? IsUnidentified { get; set; }
-
-        [ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? IsPlaceHolder { get; set; }
-
-        [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasOfficialRating { get; set; }
-
-        [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? CollapseBoxSetItems { get; set; }
-
-        public string[] GetStudios()
-        {
-            return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public string[] GetStudioIds()
-        {
-            return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public string[] GetPersonTypes()
-        {
-            return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public string[] GetPersonIds()
-        {
-            return (PersonIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public VideoType[] GetVideoTypes()
-        {
-            var val = VideoTypes;
-
-            if (string.IsNullOrEmpty(val))
-            {
-                return new VideoType[] { };
-            }
-
-            return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
-        }
+     
     }
 
 	[Route("/Reports/Items", "GET", Summary = "Gets reports based on library items")]
@@ -261,8 +100,27 @@ namespace MediaBrowser.Api.Reports
 	}
 
 	[Route("/Reports/Headers", "GET", Summary = "Gets reports headers based on library items")]
-	public class GetReportHeaders : BaseReportRequest, IReturn<List<ReportHeader>>
-	{
+    public class GetReportHeaders : IReturn<List<ReportHeader>>, IReportsHeader
+    {
+        /// <summary> Gets or sets the report view. </summary>
+        /// <value> The report view. </value>
+        [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportStatistics, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ReportView { get; set; }
+
+        /// <summary> Gets or sets the report view. </summary>
+        /// <value> The report view. </value>
+        [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string DisplayType { get; set; }
+
+        /// <summary> Gets or sets a list of types of the include items. </summary>
+        /// <value> A list of types of the include items. </value>
+        [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string IncludeItemTypes { get; set; }
+
+        /// <summary> Gets or sets the report columns. </summary>
+        /// <value> The report columns. </value>
+        [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ReportColumns { get; set; }
 	}
 
 	[Route("/Reports/Statistics", "GET", Summary = "Gets reports statistics based on library items")]
@@ -273,7 +131,7 @@ namespace MediaBrowser.Api.Reports
 	}
 
 	[Route("/Reports/Items/Download", "GET", Summary = "Downloads report")]
-	public class GetReportDownload : BaseReportRequest
+    public class GetReportDownload : BaseReportRequest, IReportsDownload
 	{
 		public GetReportDownload()
 		{
@@ -281,6 +139,66 @@ namespace MediaBrowser.Api.Reports
 		}
 
 		public ReportExportType ExportType { get; set; }
+
+        /// <summary> Gets or sets the minimum date. </summary>
+        /// <value> The minimum date. </value>
+        [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MinDate { get; set; }
+
 	}
 
+
+    [Route("/Reports/Activities", "GET", Summary = "Gets activities entries")]
+    public class GetActivityLogs : IReturn<ReportResult>, IReportsQuery, IReportsDownload
+    {
+        /// <summary> Gets or sets the report view. </summary>
+        /// <value> The report view. </value>
+        [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportStatistics, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ReportView { get; set; }
+
+        /// <summary> Gets or sets the report view. </summary>
+        /// <value> The report view. </value>
+        [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string DisplayType { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this MediaBrowser.Api.Reports.GetActivityLogs has
+        /// query limit. </summary>
+        /// <value>
+        /// true if this MediaBrowser.Api.Reports.GetActivityLogs has query limit, false if not. </value>
+        [ApiMember(Name = "HasQueryLimit", Description = "Optional. If specified, results will include all records.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool HasQueryLimit { get; set; }
+
+        /// <summary> Gets or sets who group this MediaBrowser.Api.Reports.GetActivityLogs. </summary>
+        /// <value> Describes who group this MediaBrowser.Api.Reports.GetActivityLogs. </value>
+        [ApiMember(Name = "GroupBy", Description = "Optional. If specified, results will include grouped records.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string GroupBy { get; set; }
+
+        /// <summary> Gets or sets the report columns. </summary>
+        /// <value> The report columns. </value>
+        [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ReportColumns { get; set; }
+
+        /// <summary>
+        /// Skips over a given number of items within the results. Use for paging.
+        /// </summary>
+        /// <value>The start index.</value>
+        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? StartIndex { get; set; }
+
+        /// <summary>
+        /// The maximum number of items to return
+        /// </summary>
+        /// <value>The limit.</value>
+        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? Limit { get; set; }
+
+        /// <summary> Gets or sets the minimum date. </summary>
+        /// <value> The minimum date. </value>
+        [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MinDate { get; set; }
+
+        [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string IncludeItemTypes { get; set; }
+    }
 }

+ 662 - 1135
MediaBrowser.Api/Reports/ReportsService.cs

@@ -24,1139 +24,666 @@ using System.Text;
 
 namespace MediaBrowser.Api.Reports
 {
-	/// <summary> The reports service. </summary>
-	/// <seealso cref="T:MediaBrowser.Api.BaseApiService"/>
-	public class ReportsService : BaseApiService
-	{
-
-
-		/// <summary> Manager for user. </summary>
-		private readonly IUserManager _userManager;
-
-		/// <summary> Manager for library. </summary>
-		private readonly ILibraryManager _libraryManager;
-		/// <summary> The localization. </summary>
-		private readonly ILocalizationManager _localization;
-
-		/// <summary>
-		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportsService class. </summary>
-		/// <param name="userManager"> Manager for user. </param>
-		/// <param name="libraryManager"> Manager for library. </param>
-		/// <param name="localization"> The localization. </param>
-		public ReportsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization)
-		{
-			_userManager = userManager;
-			_libraryManager = libraryManager;
-			_localization = localization;
-		}
-
-		/// <summary> Gets the given request. </summary>
-		/// <param name="request"> The request. </param>
-		/// <returns> A Task&lt;object&gt; </returns>
-		public async Task<object> Get(GetReportHeaders request)
-		{
-			if (string.IsNullOrEmpty(request.IncludeItemTypes))
-				return null;
-
-			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
-			var reportResult = reportBuilder.GetReportHeaders(reportRowType, request);
-
-			return ToOptimizedResult(reportResult);
-
-		}
-
-		/// <summary> Gets the given request. </summary>
-		/// <param name="request"> The request. </param>
-		/// <returns> A Task&lt;object&gt; </returns>
-		public async Task<object> Get(GetItemReport request)
-		{
-			if (string.IsNullOrEmpty(request.IncludeItemTypes))
-				return null;
-
-			var reportResult = await GetReportResult(request);
-
-			return ToOptimizedResult(reportResult);
-		}
-
-		/// <summary> Gets the given request. </summary>
-		/// <param name="request"> The request. </param>
-		/// <returns> A Task&lt;object&gt; </returns>
-		public async Task<object> Get(GetReportDownload request)
-		{
-			if (string.IsNullOrEmpty(request.IncludeItemTypes))
-				return null;
-
-			var headers = new Dictionary<string, string>();
-			string fileExtension = "csv";
-			string contentType = "text/plain;charset='utf-8'";
-
-			switch (request.ExportType)
-			{
-				case ReportExportType.CSV:
-					break;
-				case ReportExportType.Excel:
-					contentType = "application/vnd.ms-excel";
-					fileExtension = "xls";
-					break;
-			}
-
-			var filename = "ReportExport." + fileExtension;
-			headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
-			headers["Content-Encoding"] = "UTF-8";
-
-			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
-			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
-			ReportResult reportResult = reportBuilder.GetReportResult(queryResult.Items, reportRowType, request);
-
-			reportResult.TotalRecordCount = queryResult.TotalRecordCount;
-
-			string result = string.Empty;
-			switch (request.ExportType)
-			{
-				case ReportExportType.CSV:
-					result = new ReportExport().ExportToCsv(reportResult);
-					break;
-				case ReportExportType.Excel:
-					result = new ReportExport().ExportToExcel(reportResult);
-					break;
-			}
-
-			object ro = ResultFactory.GetResult(result, contentType, headers);
-			return ro;
-		}
-
-		/// <summary> Gets the given request. </summary>
-		/// <param name="request"> The request. </param>
-		/// <returns> A Task&lt;object&gt; </returns>
-		public async Task<object> Get(GetReportStatistics request)
-		{
-			if (string.IsNullOrEmpty(request.IncludeItemTypes))
-				return null;
-			var reportResult = await GetReportStatistic(request);
-
-			return ToOptimizedResult(reportResult);
-		}
-
-		/// <summary> Gets report statistic. </summary>
-		/// <param name="request"> The request. </param>
-		/// <returns> The report statistic. </returns>
-		private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
-		{
-			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
-
-			ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
-			ReportStatResult reportResult = reportBuilder.GetReportStatResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
-			reportResult.TotalRecordCount = reportResult.Groups.Count();
-			return reportResult;
-		}
-
-		/// <summary> Gets report result. </summary>
-		/// <param name="request"> The request. </param>
-		/// <returns> The report result. </returns>
-		private async Task<ReportResult> GetReportResult(GetItemReport request)
-		{
-
-			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
-			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
-			ReportResult reportResult = reportBuilder.GetReportResult(queryResult.Items, reportRowType, request);
-			reportResult.TotalRecordCount = queryResult.TotalRecordCount;
-
-			return reportResult;
-		}
-
-		/// <summary> Gets query result. </summary>
-		/// <param name="request"> The request. </param>
-		/// <returns> The query result. </returns>
-		private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
-		{
-			// Placeholder in case needed later
-			request.Recursive = true;
-			var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-			request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
-
-			var parentItem = string.IsNullOrEmpty(request.ParentId) ?
-				(user == null ? _libraryManager.RootFolder : user.RootFolder) :
-				_libraryManager.GetItemById(request.ParentId);
-
-			var item = string.IsNullOrEmpty(request.ParentId) ?
-				user == null ? _libraryManager.RootFolder : user.RootFolder :
-				parentItem;
-
-			IEnumerable<BaseItem> items;
-
-			if (request.Recursive)
-			{
-				var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-				return result;
-			}
-			else
-			{
-				if (user == null)
-				{
-					var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
-					return result;
-				}
-
-				var userRoot = item as UserRootFolder;
-
-				if (userRoot == null)
-				{
-					var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-
-					return result;
-				}
-
-				items = ((Folder)item).GetChildren(user, true);
-			}
-
-			return new QueryResult<BaseItem> { Items = items.ToArray() };
-
-		}
-
-		/// <summary> Gets items query. </summary>
-		/// <param name="request"> The request. </param>
-		/// <param name="user"> The user. </param>
-		/// <returns> The items query. </returns>
-		private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
-		{
-			var query = new InternalItemsQuery
-			{
-				User = user,
-				IsPlayed = request.IsPlayed,
-				MediaTypes = request.GetMediaTypes(),
-				IncludeItemTypes = request.GetIncludeItemTypes(),
-				ExcludeItemTypes = request.GetExcludeItemTypes(),
-				Recursive = true,
-				SortBy = request.GetOrderBy(),
-				SortOrder = request.SortOrder ?? SortOrder.Ascending,
-
-				Filter = i => ApplyAdditionalFilters(request, i, user, true, _libraryManager),
-				StartIndex = request.StartIndex,
-				IsMissing = request.IsMissing,
-				IsVirtualUnaired = request.IsVirtualUnaired,
-				IsUnaired = request.IsUnaired,
-				CollapseBoxSetItems = request.CollapseBoxSetItems,
-				NameLessThan = request.NameLessThan,
-				NameStartsWith = request.NameStartsWith,
-				NameStartsWithOrGreater = request.NameStartsWithOrGreater,
-				HasImdbId = request.HasImdbId,
-				IsYearMismatched = request.IsYearMismatched,
-				IsUnidentified = request.IsUnidentified,
-				IsPlaceHolder = request.IsPlaceHolder,
-				IsLocked = request.IsLocked,
-				IsInBoxSet = request.IsInBoxSet,
-				IsHD = request.IsHD,
-				Is3D = request.Is3D,
-				HasTvdbId = request.HasTvdbId,
-				HasTmdbId = request.HasTmdbId,
-				HasOverview = request.HasOverview,
-				HasOfficialRating = request.HasOfficialRating,
-				HasParentalRating = request.HasParentalRating,
-				HasSpecialFeature = request.HasSpecialFeature,
-				HasSubtitles = request.HasSubtitles,
-				HasThemeSong = request.HasThemeSong,
-				HasThemeVideo = request.HasThemeVideo,
-				HasTrailer = request.HasTrailer,
-				Tags = request.GetTags(),
-				OfficialRatings = request.GetOfficialRatings(),
-				Genres = request.GetGenres(),
-				Studios = request.GetStudios(),
-				StudioIds = request.GetStudioIds(),
-				Person = request.Person,
-				PersonIds = request.GetPersonIds(),
-				PersonTypes = request.GetPersonTypes(),
-				Years = request.GetYears(),
-				ImageTypes = request.GetImageTypes().ToArray(),
-				VideoTypes = request.GetVideoTypes().ToArray(),
-				AdjacentTo = request.AdjacentTo
-			};
-
-			if (!string.IsNullOrWhiteSpace(request.Ids))
-			{
-				query.CollapseBoxSetItems = false;
-			}
-
-			foreach (var filter in request.GetFilters())
-			{
-				switch (filter)
-				{
-					case ItemFilter.Dislikes:
-						query.IsLiked = false;
-						break;
-					case ItemFilter.IsFavorite:
-						query.IsFavorite = true;
-						break;
-					case ItemFilter.IsFavoriteOrLikes:
-						query.IsFavoriteOrLiked = true;
-						break;
-					case ItemFilter.IsFolder:
-						query.IsFolder = true;
-						break;
-					case ItemFilter.IsNotFolder:
-						query.IsFolder = false;
-						break;
-					case ItemFilter.IsPlayed:
-						query.IsPlayed = true;
-						break;
-					case ItemFilter.IsRecentlyAdded:
-						break;
-					case ItemFilter.IsResumable:
-						query.IsResumable = true;
-						break;
-					case ItemFilter.IsUnplayed:
-						query.IsPlayed = false;
-						break;
-					case ItemFilter.Likes:
-						query.IsLiked = true;
-						break;
-				}
-			}
-
-			if (request.HasQueryLimit)
-				query.Limit = request.Limit;
-			return query;
-		}
-
-		/// <summary> Applies filtering. </summary>
-		/// <param name="items"> The items. </param>
-		/// <param name="filter"> The filter. </param>
-		/// <param name="user"> The user. </param>
-		/// <param name="repository"> The repository. </param>
-		/// <returns> IEnumerable{BaseItem}. </returns>
-		internal static IEnumerable<BaseItem> ApplyFilter(IEnumerable<BaseItem> items, ItemFilter filter, User user, IUserDataManager repository)
-		{
-			// Avoid implicitly captured closure
-			var currentUser = user;
-
-			switch (filter)
-			{
-				case ItemFilter.IsFavoriteOrLikes:
-					return items.Where(item =>
-					{
-						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-						if (userdata == null)
-						{
-							return false;
-						}
-
-						var likes = userdata.Likes ?? false;
-						var favorite = userdata.IsFavorite;
-
-						return likes || favorite;
-					});
-
-				case ItemFilter.Likes:
-					return items.Where(item =>
-					{
-						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-						return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
-					});
-
-				case ItemFilter.Dislikes:
-					return items.Where(item =>
-					{
-						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-						return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
-					});
-
-				case ItemFilter.IsFavorite:
-					return items.Where(item =>
-					{
-						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-						return userdata != null && userdata.IsFavorite;
-					});
-
-				case ItemFilter.IsResumable:
-					return items.Where(item =>
-					{
-						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-						return userdata != null && userdata.PlaybackPositionTicks > 0;
-					});
-
-				case ItemFilter.IsPlayed:
-					return items.Where(item => item.IsPlayed(currentUser));
-
-				case ItemFilter.IsUnplayed:
-					return items.Where(item => item.IsUnplayed(currentUser));
-
-				case ItemFilter.IsFolder:
-					return items.Where(item => item.IsFolder);
-
-				case ItemFilter.IsNotFolder:
-					return items.Where(item => !item.IsFolder);
-
-				case ItemFilter.IsRecentlyAdded:
-					return items.Where(item => (DateTime.UtcNow - item.DateCreated).TotalDays <= 10);
-			}
-
-			return items;
-		}
-
-		/// <summary> Applies the additional filters. </summary>
-		/// <param name="request"> The request. </param>
-		/// <param name="i"> Zero-based index of the. </param>
-		/// <param name="user"> The user. </param>
-		/// <param name="isPreFiltered"> true if this object is pre filtered. </param>
-		/// <param name="libraryManager"> Manager for library. </param>
-		/// <returns> true if it succeeds, false if it fails. </returns>
-        private bool ApplyAdditionalFilters(BaseReportRequest request, BaseItem i, User user, bool isPreFiltered, ILibraryManager libraryManager)
-		{
-			var video = i as Video;
-
-			if (!isPreFiltered)
-			{
-				var mediaTypes = request.GetMediaTypes();
-				if (mediaTypes.Length > 0)
-				{
-					if (!(!string.IsNullOrEmpty(i.MediaType) && mediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
-					{
-						return false;
-					}
-				}
-
-				if (request.IsPlayed.HasValue)
-				{
-					var val = request.IsPlayed.Value;
-					if (i.IsPlayed(user) != val)
-					{
-						return false;
-					}
-				}
-
-				// Exclude item types
-				var excluteItemTypes = request.GetExcludeItemTypes();
-				if (excluteItemTypes.Length > 0 && excluteItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
-				{
-					return false;
-				}
-
-				// Include item types
-				var includeItemTypes = request.GetIncludeItemTypes();
-				if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
-				{
-					return false;
-				}
-
-				if (request.IsInBoxSet.HasValue)
-				{
-					var val = request.IsInBoxSet.Value;
-					if (i.Parents.OfType<BoxSet>().Any() != val)
-					{
-						return false;
-					}
-				}
-
-				// Filter by Video3DFormat
-				if (request.Is3D.HasValue)
-				{
-					var val = request.Is3D.Value;
-
-					if (video == null || val != video.Video3DFormat.HasValue)
-					{
-						return false;
-					}
-				}
-
-				if (request.IsHD.HasValue)
-				{
-					var val = request.IsHD.Value;
-
-					if (video == null || val != video.IsHD)
-					{
-						return false;
-					}
-				}
-
-				if (request.IsUnidentified.HasValue)
-				{
-					var val = request.IsUnidentified.Value;
-					if (i.IsUnidentified != val)
-					{
-						return false;
-					}
-				}
-
-				if (request.IsLocked.HasValue)
-				{
-					var val = request.IsLocked.Value;
-					if (i.IsLocked != val)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasOverview.HasValue)
-				{
-					var filterValue = request.HasOverview.Value;
-
-					var hasValue = !string.IsNullOrEmpty(i.Overview);
-
-					if (hasValue != filterValue)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasImdbId.HasValue)
-				{
-					var filterValue = request.HasImdbId.Value;
-
-					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Imdb));
-
-					if (hasValue != filterValue)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasTmdbId.HasValue)
-				{
-					var filterValue = request.HasTmdbId.Value;
-
-					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb));
-
-					if (hasValue != filterValue)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasTvdbId.HasValue)
-				{
-					var filterValue = request.HasTvdbId.Value;
-
-					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb));
-
-					if (hasValue != filterValue)
-					{
-						return false;
-					}
-				}
-
-				if (request.IsYearMismatched.HasValue)
-				{
-					var filterValue = request.IsYearMismatched.Value;
-
-					if (UserViewBuilder.IsYearMismatched(i, libraryManager) != filterValue)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasOfficialRating.HasValue)
-				{
-					var filterValue = request.HasOfficialRating.Value;
-
-					var hasValue = !string.IsNullOrEmpty(i.OfficialRating);
-
-					if (hasValue != filterValue)
-					{
-						return false;
-					}
-				}
-
-				if (request.IsPlaceHolder.HasValue)
-				{
-					var filterValue = request.IsPlaceHolder.Value;
-
-					var isPlaceHolder = false;
-
-					var hasPlaceHolder = i as ISupportsPlaceHolders;
-
-					if (hasPlaceHolder != null)
-					{
-						isPlaceHolder = hasPlaceHolder.IsPlaceHolder;
-					}
-
-					if (isPlaceHolder != filterValue)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasSpecialFeature.HasValue)
-				{
-					var filterValue = request.HasSpecialFeature.Value;
-
-					var movie = i as IHasSpecialFeatures;
-
-					if (movie != null)
-					{
-						var ok = filterValue
-							? movie.SpecialFeatureIds.Count > 0
-							: movie.SpecialFeatureIds.Count == 0;
-
-						if (!ok)
-						{
-							return false;
-						}
-					}
-					else
-					{
-						return false;
-					}
-				}
-
-				if (request.HasSubtitles.HasValue)
-				{
-					var val = request.HasSubtitles.Value;
-
-					if (video == null || val != video.HasSubtitles)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasParentalRating.HasValue)
-				{
-					var val = request.HasParentalRating.Value;
-
-					var rating = i.CustomRating;
-
-					if (string.IsNullOrEmpty(rating))
-					{
-						rating = i.OfficialRating;
-					}
-
-					if (val)
-					{
-						if (string.IsNullOrEmpty(rating))
-						{
-							return false;
-						}
-					}
-					else
-					{
-						if (!string.IsNullOrEmpty(rating))
-						{
-							return false;
-						}
-					}
-				}
-
-				if (request.HasTrailer.HasValue)
-				{
-					var val = request.HasTrailer.Value;
-					var trailerCount = 0;
-
-					var hasTrailers = i as IHasTrailers;
-					if (hasTrailers != null)
-					{
-						trailerCount = hasTrailers.GetTrailerIds().Count;
-					}
-
-					var ok = val ? trailerCount > 0 : trailerCount == 0;
-
-					if (!ok)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasThemeSong.HasValue)
-				{
-					var filterValue = request.HasThemeSong.Value;
-
-					var themeCount = 0;
-					var iHasThemeMedia = i as IHasThemeMedia;
-
-					if (iHasThemeMedia != null)
-					{
-						themeCount = iHasThemeMedia.ThemeSongIds.Count;
-					}
-					var ok = filterValue ? themeCount > 0 : themeCount == 0;
-
-					if (!ok)
-					{
-						return false;
-					}
-				}
-
-				if (request.HasThemeVideo.HasValue)
-				{
-					var filterValue = request.HasThemeVideo.Value;
-
-					var themeCount = 0;
-					var iHasThemeMedia = i as IHasThemeMedia;
-
-					if (iHasThemeMedia != null)
-					{
-						themeCount = iHasThemeMedia.ThemeVideoIds.Count;
-					}
-					var ok = filterValue ? themeCount > 0 : themeCount == 0;
-
-					if (!ok)
-					{
-						return false;
-					}
-				}
-
-				// Apply tag filter
-				var tags = request.GetTags();
-				if (tags.Length > 0)
-				{
-					var hasTags = i as IHasTags;
-					if (hasTags == null)
-					{
-						return false;
-					}
-					if (!(tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase))))
-					{
-						return false;
-					}
-				}
-
-				// Apply official rating filter
-				var officialRatings = request.GetOfficialRatings();
-				if (officialRatings.Length > 0 && !officialRatings.Contains(i.OfficialRating ?? string.Empty))
-				{
-					return false;
-				}
-
-				// Apply genre filter
-				var genres = request.GetGenres();
-				if (genres.Length > 0 && !(genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))))
-				{
-					return false;
-				}
-
-				// Filter by VideoType
-				var videoTypes = request.GetVideoTypes();
-				if (videoTypes.Length > 0 && (video == null || !videoTypes.Contains(video.VideoType)))
-				{
-					return false;
-				}
-
-				var imageTypes = request.GetImageTypes().ToList();
-				if (imageTypes.Count > 0)
-				{
-					if (!(imageTypes.Any(i.HasImage)))
-					{
-						return false;
-					}
-				}
-
-				// Apply studio filter
-				var studios = request.GetStudios();
-				if (studios.Length > 0 && !studios.Any(v => i.Studios.Contains(v, StringComparer.OrdinalIgnoreCase)))
-				{
-					return false;
-				}
-
-				// Apply studio filter
-				var studioIds = request.GetStudioIds();
-				if (studioIds.Length > 0 && !studioIds.Any(id =>
-				{
-					var studioItem = libraryManager.GetItemById(id);
-					return studioItem != null && i.Studios.Contains(studioItem.Name, StringComparer.OrdinalIgnoreCase);
-				}))
-				{
-					return false;
-				}
-
-				// Apply year filter
-				var years = request.GetYears();
-				if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value)))
-				{
-					return false;
-				}
-
-				// Apply person filter
-				var personIds = request.GetPersonIds();
-				if (personIds.Length > 0)
-				{
-					var names = personIds
-						.Select(libraryManager.GetItemById)
-						.Select(p => p == null ? "-1" : p.Name)
-						.ToList();
-
-					if (!(names.Any(v => _libraryManager.GetPeople(i).Select(p => p.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
-					{
-						return false;
-					}
-				}
-
-				// Apply person filter
-				if (!string.IsNullOrEmpty(request.Person))
-				{
-					var personTypes = request.GetPersonTypes();
-
-					if (personTypes.Length == 0)
-					{
-                        if (!(_libraryManager.GetPeople(i).Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase))))
-						{
-							return false;
-						}
-					}
-					else
-					{
-						var types = personTypes;
-
-						var ok = new[] { i }.Any(item =>
-                                _libraryManager.GetPeople(i).Any(p =>
-									p.Name.Equals(request.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase))));
-
-						if (!ok)
-						{
-							return false;
-						}
-					}
-				}
-			}
-
-			if (request.MinCommunityRating.HasValue)
-			{
-				var val = request.MinCommunityRating.Value;
-
-				if (!(i.CommunityRating.HasValue && i.CommunityRating >= val))
-				{
-					return false;
-				}
-			}
-
-			if (request.MinCriticRating.HasValue)
-			{
-				var val = request.MinCriticRating.Value;
-
-				var hasCriticRating = i as IHasCriticRating;
-
-				if (hasCriticRating != null)
-				{
-					if (!(hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val))
-					{
-						return false;
-					}
-				}
-				else
-				{
-					return false;
-				}
-			}
-
-			// Artists
-			if (!string.IsNullOrEmpty(request.ArtistIds))
-			{
-				var artistIds = request.ArtistIds.Split('|');
-
-				var audio = i as IHasArtist;
-
-				if (!(audio != null && artistIds.Any(id =>
-				{
-					var artistItem = libraryManager.GetItemById(id);
-					return artistItem != null && audio.HasAnyArtist(artistItem.Name);
-				})))
-				{
-					return false;
-				}
-			}
-
-			// Artists
-			if (!string.IsNullOrEmpty(request.Artists))
-			{
-				var artists = request.Artists.Split('|');
-
-				var audio = i as IHasArtist;
-
-				if (!(audio != null && artists.Any(audio.HasAnyArtist)))
-				{
-					return false;
-				}
-			}
-
-			// Albums
-			if (!string.IsNullOrEmpty(request.Albums))
-			{
-				var albums = request.Albums.Split('|');
-
-				var audio = i as Audio;
-
-				if (audio != null)
-				{
-					if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase)))
-					{
-						return false;
-					}
-				}
-
-				var album = i as MusicAlbum;
-
-				if (album != null)
-				{
-					if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase)))
-					{
-						return false;
-					}
-				}
-
-				var musicVideo = i as MusicVideo;
-
-				if (musicVideo != null)
-				{
-					if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase)))
-					{
-						return false;
-					}
-				}
-
-				return false;
-			}
-
-			// Min index number
-			if (request.MinIndexNumber.HasValue)
-			{
-				if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value))
-				{
-					return false;
-				}
-			}
-
-			// Min official rating
-			if (!string.IsNullOrEmpty(request.MinOfficialRating))
-			{
-				var level = _localization.GetRatingLevel(request.MinOfficialRating);
-
-				if (level.HasValue)
-				{
-					var rating = i.CustomRating;
-
-					if (string.IsNullOrEmpty(rating))
-					{
-						rating = i.OfficialRating;
-					}
-
-					if (!string.IsNullOrEmpty(rating))
-					{
-						var itemLevel = _localization.GetRatingLevel(rating);
-
-						if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value))
-						{
-							return false;
-						}
-					}
-				}
-			}
-
-			// Max official rating
-			if (!string.IsNullOrEmpty(request.MaxOfficialRating))
-			{
-				var level = _localization.GetRatingLevel(request.MaxOfficialRating);
-
-				if (level.HasValue)
-				{
-					var rating = i.CustomRating;
-
-					if (string.IsNullOrEmpty(rating))
-					{
-						rating = i.OfficialRating;
-					}
-
-					if (!string.IsNullOrEmpty(rating))
-					{
-						var itemLevel = _localization.GetRatingLevel(rating);
-
-						if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value))
-						{
-							return false;
-						}
-					}
-				}
-			}
-
-			// LocationTypes
-			if (!string.IsNullOrEmpty(request.LocationTypes))
-			{
-				var vals = request.LocationTypes.Split(',');
-				if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
-				{
-					return false;
-				}
-			}
-
-			// ExcludeLocationTypes
-			if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
-			{
-				var vals = request.ExcludeLocationTypes.Split(',');
-				if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
-				{
-					return false;
-				}
-			}
-
-			if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater))
-			{
-				var ok = new[] { i }.OfType<IHasAlbumArtist>()
-					.Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1);
-
-				if (!ok)
-				{
-					return false;
-				}
-			}
-
-			// Filter by Series Status
-			if (!string.IsNullOrEmpty(request.SeriesStatus))
-			{
-				var vals = request.SeriesStatus.Split(',');
-
-				var ok = new[] { i }.OfType<Series>().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase));
-
-				if (!ok)
-				{
-					return false;
-				}
-			}
-
-			// Filter by Series AirDays
-			if (!string.IsNullOrEmpty(request.AirDays))
-			{
-				var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true));
-
-				var ok = new[] { i }.OfType<Series>().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d)));
-
-				if (!ok)
-				{
-					return false;
-				}
-			}
-
-			if (request.MinPlayers.HasValue)
-			{
-				var filterValue = request.MinPlayers.Value;
-
-				var game = i as Game;
-
-				if (game != null)
-				{
-					var players = game.PlayersSupported ?? 1;
-
-					var ok = players >= filterValue;
-
-					if (!ok)
-					{
-						return false;
-					}
-				}
-				else
-				{
-					return false;
-				}
-			}
-
-			if (request.MaxPlayers.HasValue)
-			{
-				var filterValue = request.MaxPlayers.Value;
-
-				var game = i as Game;
-
-				if (game != null)
-				{
-					var players = game.PlayersSupported ?? 1;
-
-					var ok = players <= filterValue;
-
-					if (!ok)
-					{
-						return false;
-					}
-				}
-				else
-				{
-					return false;
-				}
-			}
-
-			if (request.ParentIndexNumber.HasValue)
-			{
-				var filterValue = request.ParentIndexNumber.Value;
-
-				var episode = i as Episode;
-
-				if (episode != null)
-				{
-					if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue)
-					{
-						return false;
-					}
-				}
-
-				var song = i as Audio;
-
-				if (song != null)
-				{
-					if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue)
-					{
-						return false;
-					}
-				}
-			}
-
-			if (request.AiredDuringSeason.HasValue)
-			{
-				var episode = i as Episode;
-
-				if (episode == null)
-				{
-					return false;
-				}
-
-				if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any())
-				{
-					return false;
-				}
-			}
-
-			if (!string.IsNullOrEmpty(request.MinPremiereDate))
-			{
-				var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-
-				if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date))
-				{
-					return false;
-				}
-			}
-
-			if (!string.IsNullOrEmpty(request.MaxPremiereDate))
-			{
-				var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-
-				if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date))
-				{
-					return false;
-				}
-			}
-
-			return true;
-		}
-
-		/// <summary> Applies the paging. </summary>
-		/// <param name="request"> The request. </param>
-		/// <param name="items"> The items. </param>
-		/// <returns> IEnumerable{BaseItem}. </returns>
-		private IEnumerable<BaseItem> ApplyPaging(GetItems request, IEnumerable<BaseItem> items)
-		{
-			// Start at
-			if (request.StartIndex.HasValue)
-			{
-				items = items.Skip(request.StartIndex.Value);
-			}
-
-			// Return limit
-			if (request.Limit.HasValue)
-			{
-				items = items.Take(request.Limit.Value);
-			}
-
-			return items;
-		}
-
-	}
+    /// <summary> The reports service. </summary>
+    /// <seealso cref="T:MediaBrowser.Api.BaseApiService"/>
+    public class ReportsService : BaseApiService
+    {
+        #region [Constructors]
+
+        /// <summary>
+        /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportsService class. </summary>
+        /// <param name="userManager"> Manager for user. </param>
+        /// <param name="libraryManager"> Manager for library. </param>
+        /// <param name="localization"> The localization. </param>
+        /// <param name="activityManager"> Manager for activity. </param>
+        public ReportsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization, IActivityManager activityManager, IActivityRepository repo)
+        {
+            _userManager = userManager;
+            _libraryManager = libraryManager;
+            _localization = localization;
+            _activityManager = activityManager;
+            _repo = repo;
+        }
+
+        #endregion
+
+        #region [Private Fields]
+
+        private readonly IActivityManager _activityManager; ///< Manager for activity
+
+        /// <summary> Manager for library. </summary>
+        private readonly ILibraryManager _libraryManager;   ///< Manager for library
+        /// <summary> The localization. </summary>
+
+        private readonly ILocalizationManager _localization;    ///< The localization
+
+        private readonly IActivityRepository _repo;
+
+        /// <summary> Manager for user. </summary>
+        private readonly IUserManager _userManager; ///< Manager for user
+
+        #endregion
+
+        #region [Public Methods]
+
+        /// <summary> Gets the given request. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> A Task&lt;object&gt; </returns>
+        public async Task<object> Get(GetActivityLogs request)
+        {
+            request.DisplayType = "Screen";
+            ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
+            return ToOptimizedResult(result);
+        }
+
+        /// <summary> Gets the given request. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> A Task&lt;object&gt; </returns>
+        public async Task<object> Get(GetReportHeaders request)
+        {
+            if (string.IsNullOrEmpty(request.IncludeItemTypes))
+                return null;
+
+            request.DisplayType = "Screen";
+            ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView);
+
+            List<ReportHeader> result = new List<ReportHeader>();
+            switch (reportViewType)
+            {
+                case ReportViewType.ReportData:
+                    ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
+                    result = dataBuilder.GetHeaders(request);
+                    break;
+                case ReportViewType.ReportStatistics:
+                    break;
+                case ReportViewType.ReportActivities:
+                    ReportActivitiesBuilder activityBuilder = new ReportActivitiesBuilder(_libraryManager, _userManager);
+                    result = activityBuilder.GetHeaders(request);
+                    break;
+            }
+
+            return ToOptimizedResult(result);
+
+        }
+
+        /// <summary> Gets the given request. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> A Task&lt;object&gt; </returns>
+        public async Task<object> Get(GetItemReport request)
+        {
+            if (string.IsNullOrEmpty(request.IncludeItemTypes))
+                return null;
+
+            request.DisplayType = "Screen";
+            var reportResult = await GetReportResult(request);
+
+            return ToOptimizedResult(reportResult);
+        }
+
+        /// <summary> Gets the given request. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> A Task&lt;object&gt; </returns>
+        public async Task<object> Get(GetReportStatistics request)
+        {
+            if (string.IsNullOrEmpty(request.IncludeItemTypes))
+                return null;
+            request.DisplayType = "Screen";
+            var reportResult = await GetReportStatistic(request);
+
+            return ToOptimizedResult(reportResult);
+        }
+
+        /// <summary> Gets the given request. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> A Task&lt;object&gt; </returns>
+        public async Task<object> Get(GetReportDownload request)
+        {
+            if (string.IsNullOrEmpty(request.IncludeItemTypes))
+                return null;
+
+            request.DisplayType = "Export";
+            ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView);
+            var headers = new Dictionary<string, string>();
+            string fileExtension = "csv";
+            string contentType = "text/plain;charset='utf-8'";
+
+            switch (request.ExportType)
+            {
+                case ReportExportType.CSV:
+                    break;
+                case ReportExportType.Excel:
+                    contentType = "application/vnd.ms-excel";
+                    fileExtension = "xls";
+                    break;
+            }
+
+            var filename = "ReportExport." + fileExtension;
+            headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
+            headers["Content-Encoding"] = "UTF-8";
+
+            ReportResult result = null;
+            switch (reportViewType)
+            {
+                case ReportViewType.ReportStatistics:
+                case ReportViewType.ReportData:
+                    ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+                    ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
+                    QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+                    result = dataBuilder.GetResult(queryResult.Items, request);
+                    result.TotalRecordCount = queryResult.TotalRecordCount;
+                    break;
+                case ReportViewType.ReportActivities:
+                    result = await GetReportActivities(request).ConfigureAwait(false);
+                    break;
+            }
+
+            string returnResult = string.Empty;
+            switch (request.ExportType)
+            {
+                case ReportExportType.CSV:
+                    returnResult = new ReportExport().ExportToCsv(result);
+                    break;
+                case ReportExportType.Excel:
+                    returnResult = new ReportExport().ExportToExcel(result);
+                    break;
+            }
+
+            object ro = ResultFactory.GetResult(returnResult, contentType, headers);
+            return ro;
+        }
+
+        #endregion
+
+        #region [Private Methods]
+
+        /// <summary> Gets items query. </summary>
+        /// <param name="request"> The request. </param>
+        /// <param name="user"> The user. </param>
+        /// <returns> The items query. </returns>
+        private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
+        {
+            var query = new InternalItemsQuery
+            {
+                User = user,
+                IsPlayed = request.IsPlayed,
+                MediaTypes = request.GetMediaTypes(),
+                IncludeItemTypes = request.GetIncludeItemTypes(),
+                ExcludeItemTypes = request.GetExcludeItemTypes(),
+                Recursive = request.Recursive,
+                SortBy = request.GetOrderBy(),
+                SortOrder = request.SortOrder ?? SortOrder.Ascending,
+
+                Filter = i => ApplyAdditionalFilters(request, i, user, _libraryManager),
+
+                Limit = request.Limit,
+                StartIndex = request.StartIndex,
+                IsMissing = request.IsMissing,
+                IsVirtualUnaired = request.IsVirtualUnaired,
+                IsUnaired = request.IsUnaired,
+                CollapseBoxSetItems = request.CollapseBoxSetItems,
+                NameLessThan = request.NameLessThan,
+                NameStartsWith = request.NameStartsWith,
+                NameStartsWithOrGreater = request.NameStartsWithOrGreater,
+                HasImdbId = request.HasImdbId,
+                IsYearMismatched = request.IsYearMismatched,
+                IsUnidentified = request.IsUnidentified,
+                IsPlaceHolder = request.IsPlaceHolder,
+                IsLocked = request.IsLocked,
+                IsInBoxSet = request.IsInBoxSet,
+                IsHD = request.IsHD,
+                Is3D = request.Is3D,
+                HasTvdbId = request.HasTvdbId,
+                HasTmdbId = request.HasTmdbId,
+                HasOverview = request.HasOverview,
+                HasOfficialRating = request.HasOfficialRating,
+                HasParentalRating = request.HasParentalRating,
+                HasSpecialFeature = request.HasSpecialFeature,
+                HasSubtitles = request.HasSubtitles,
+                HasThemeSong = request.HasThemeSong,
+                HasThemeVideo = request.HasThemeVideo,
+                HasTrailer = request.HasTrailer,
+                Tags = request.GetTags(),
+                OfficialRatings = request.GetOfficialRatings(),
+                Genres = request.GetGenres(),
+                Studios = request.GetStudios(),
+                StudioIds = request.GetStudioIds(),
+                Person = request.Person,
+                PersonIds = request.GetPersonIds(),
+                PersonTypes = request.GetPersonTypes(),
+                Years = request.GetYears(),
+                ImageTypes = request.GetImageTypes().ToArray(),
+                VideoTypes = request.GetVideoTypes().ToArray(),
+                AdjacentTo = request.AdjacentTo,
+                ItemIds = request.GetItemIds(),
+                MinPlayers = request.MinPlayers,
+                MaxPlayers = request.MaxPlayers,
+                MinCommunityRating = request.MinCommunityRating,
+                MinCriticRating = request.MinCriticRating
+            };
+
+            if (!string.IsNullOrWhiteSpace(request.Ids))
+            {
+                query.CollapseBoxSetItems = false;
+            }
+
+            foreach (var filter in request.GetFilters())
+            {
+                switch (filter)
+                {
+                    case ItemFilter.Dislikes:
+                        query.IsLiked = false;
+                        break;
+                    case ItemFilter.IsFavorite:
+                        query.IsFavorite = true;
+                        break;
+                    case ItemFilter.IsFavoriteOrLikes:
+                        query.IsFavoriteOrLiked = true;
+                        break;
+                    case ItemFilter.IsFolder:
+                        query.IsFolder = true;
+                        break;
+                    case ItemFilter.IsNotFolder:
+                        query.IsFolder = false;
+                        break;
+                    case ItemFilter.IsPlayed:
+                        query.IsPlayed = true;
+                        break;
+                    case ItemFilter.IsRecentlyAdded:
+                        break;
+                    case ItemFilter.IsResumable:
+                        query.IsResumable = true;
+                        break;
+                    case ItemFilter.IsUnplayed:
+                        query.IsPlayed = false;
+                        break;
+                    case ItemFilter.Likes:
+                        query.IsLiked = true;
+                        break;
+                }
+            }
+
+            if (request.HasQueryLimit)
+                query.Limit = request.Limit;
+
+            return query;
+        }
+
+        private bool ApplyAdditionalFilters(BaseReportRequest request, BaseItem i, User user, ILibraryManager libraryManager)
+        {
+            // Artists
+            if (!string.IsNullOrEmpty(request.ArtistIds))
+            {
+                var artistIds = request.ArtistIds.Split(new[] { '|', ',' });
+
+                var audio = i as IHasArtist;
+
+                if (!(audio != null && artistIds.Any(id =>
+                {
+                    var artistItem = libraryManager.GetItemById(id);
+                    return artistItem != null && audio.HasAnyArtist(artistItem.Name);
+                })))
+                {
+                    return false;
+                }
+            }
+
+            // Artists
+            if (!string.IsNullOrEmpty(request.Artists))
+            {
+                var artists = request.Artists.Split('|');
+
+                var audio = i as IHasArtist;
+
+                if (!(audio != null && artists.Any(audio.HasAnyArtist)))
+                {
+                    return false;
+                }
+            }
+
+            // Albums
+            if (!string.IsNullOrEmpty(request.Albums))
+            {
+                var albums = request.Albums.Split('|');
+
+                var audio = i as Audio;
+
+                if (audio != null)
+                {
+                    if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase)))
+                    {
+                        return false;
+                    }
+                }
+
+                var album = i as MusicAlbum;
+
+                if (album != null)
+                {
+                    if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase)))
+                    {
+                        return false;
+                    }
+                }
+
+                var musicVideo = i as MusicVideo;
+
+                if (musicVideo != null)
+                {
+                    if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase)))
+                    {
+                        return false;
+                    }
+                }
+
+                return false;
+            }
+
+            // Min index number
+            if (request.MinIndexNumber.HasValue)
+            {
+                if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value))
+                {
+                    return false;
+                }
+            }
+
+            // Min official rating
+            if (!string.IsNullOrEmpty(request.MinOfficialRating))
+            {
+                var level = _localization.GetRatingLevel(request.MinOfficialRating);
+
+                if (level.HasValue)
+                {
+                    var rating = i.CustomRating;
+
+                    if (string.IsNullOrEmpty(rating))
+                    {
+                        rating = i.OfficialRating;
+                    }
+
+                    if (!string.IsNullOrEmpty(rating))
+                    {
+                        var itemLevel = _localization.GetRatingLevel(rating);
+
+                        if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value))
+                        {
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            // Max official rating
+            if (!string.IsNullOrEmpty(request.MaxOfficialRating))
+            {
+                var level = _localization.GetRatingLevel(request.MaxOfficialRating);
+
+                if (level.HasValue)
+                {
+                    var rating = i.CustomRating;
+
+                    if (string.IsNullOrEmpty(rating))
+                    {
+                        rating = i.OfficialRating;
+                    }
+
+                    if (!string.IsNullOrEmpty(rating))
+                    {
+                        var itemLevel = _localization.GetRatingLevel(rating);
+
+                        if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value))
+                        {
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            // LocationTypes
+            if (!string.IsNullOrEmpty(request.LocationTypes))
+            {
+                var vals = request.LocationTypes.Split(',');
+                if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
+                {
+                    return false;
+                }
+            }
+
+            // ExcludeLocationTypes
+            if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
+            {
+                var vals = request.ExcludeLocationTypes.Split(',');
+                if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
+                {
+                    return false;
+                }
+            }
+
+            if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater))
+            {
+                var ok = new[] { i }.OfType<IHasAlbumArtist>()
+                    .Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1);
+
+                if (!ok)
+                {
+                    return false;
+                }
+            }
+
+            // Filter by Series Status
+            if (!string.IsNullOrEmpty(request.SeriesStatus))
+            {
+                var vals = request.SeriesStatus.Split(',');
+
+                var ok = new[] { i }.OfType<Series>().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase));
+
+                if (!ok)
+                {
+                    return false;
+                }
+            }
+
+            // Filter by Series AirDays
+            if (!string.IsNullOrEmpty(request.AirDays))
+            {
+                var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true));
+
+                var ok = new[] { i }.OfType<Series>().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d)));
+
+                if (!ok)
+                {
+                    return false;
+                }
+            }
+
+            if (request.ParentIndexNumber.HasValue)
+            {
+                var filterValue = request.ParentIndexNumber.Value;
+
+                var episode = i as Episode;
+
+                if (episode != null)
+                {
+                    if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue)
+                    {
+                        return false;
+                    }
+                }
+
+                var song = i as Audio;
+
+                if (song != null)
+                {
+                    if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue)
+                    {
+                        return false;
+                    }
+                }
+            }
+
+            if (request.AiredDuringSeason.HasValue)
+            {
+                var episode = i as Episode;
+
+                if (episode == null)
+                {
+                    return false;
+                }
+
+                if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any())
+                {
+                    return false;
+                }
+            }
+
+            if (!string.IsNullOrEmpty(request.MinPremiereDate))
+            {
+                var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+
+                if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date))
+                {
+                    return false;
+                }
+            }
+
+            if (!string.IsNullOrEmpty(request.MaxPremiereDate))
+            {
+                var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+
+                if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary> Applies the paging. </summary>
+        /// <param name="request"> The request. </param>
+        /// <param name="items"> The items. </param>
+        /// <returns> IEnumerable{BaseItem}. </returns>
+        private IEnumerable<BaseItem> ApplyPaging(BaseReportRequest request, IEnumerable<BaseItem> items)
+        {
+            // Start at
+            if (request.StartIndex.HasValue)
+            {
+                items = items.Skip(request.StartIndex.Value);
+            }
+
+            // Return limit
+            if (request.Limit.HasValue)
+            {
+                items = items.Take(request.Limit.Value);
+            }
+
+            return items;
+        }
+
+        /// <summary> Gets query result. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> The query result. </returns>
+        private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
+        {
+            // Placeholder in case needed later
+            request.Recursive = true;
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+            request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
+
+            var parentItem = string.IsNullOrEmpty(request.ParentId) ?
+                (user == null ? _libraryManager.RootFolder : user.RootFolder) :
+                _libraryManager.GetItemById(request.ParentId);
+
+            var item = string.IsNullOrEmpty(request.ParentId) ?
+                user == null ? _libraryManager.RootFolder : user.RootFolder :
+                parentItem;
+
+            IEnumerable<BaseItem> items;
+
+            if (request.Recursive)
+            {
+                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+                return result;
+            }
+            else
+            {
+                if (user == null)
+                {
+                    var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+                    return result;
+                }
+
+                var userRoot = item as UserRootFolder;
+
+                if (userRoot == null)
+                {
+                    var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+
+                    return result;
+                }
+
+                items = ((Folder)item).GetChildren(user, true);
+            }
+
+            return new QueryResult<BaseItem> { Items = items.ToArray() };
+
+        }
+
+        /// <summary> Gets report activities. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> The report activities. </returns>
+        private Task<ReportResult> GetReportActivities(IReportsDownload request)
+        {
+            return Task<ReportResult>.Run(() =>
+            {
+                DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
+                (DateTime?)null :
+                DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+
+                QueryResult<ActivityLogEntry> queryResult;
+                 if (request.HasQueryLimit)   
+                   queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
+                 else
+                     queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
+                //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
+
+                ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
+                var result = builder.GetResult(queryResult, request);
+                result.TotalRecordCount = queryResult.TotalRecordCount;
+                return result;
+
+            });
+
+        }
+
+        /// <summary> Gets report result. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> The report result. </returns>
+        private async Task<ReportResult> GetReportResult(GetItemReport request)
+        {
+            ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
+            QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+            ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
+            reportResult.TotalRecordCount = queryResult.TotalRecordCount;
+
+            return reportResult;
+        }
+
+        /// <summary> Gets report statistic. </summary>
+        /// <param name="request"> The request. </param>
+        /// <returns> The report statistic. </returns>
+        private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
+        {
+            ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+            QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+
+            ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
+            ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
+            reportResult.TotalRecordCount = reportResult.Groups.Count();
+            return reportResult;
+        }
+
+        #endregion
+
+    }
 }

+ 272 - 202
MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs

@@ -9,206 +9,276 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Reports
 {
-	/// <summary> A report stat builder. </summary>
-	/// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
-	public class ReportStatBuilder : ReportBuilderBase
-	{
-		/// <summary>
-		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. </summary>
-		/// <param name="libraryManager"> Manager for library. </param>
-		public ReportStatBuilder(ILibraryManager libraryManager)
-			: base(libraryManager)
-		{
-		}
-
-		/// <summary> Gets report stat result. </summary>
-		/// <param name="items"> The items. </param>
-		/// <param name="reportRowType"> Type of the report row. </param>
-		/// <param name="topItem"> The top item. </param>
-		/// <returns> The report stat result. </returns>
-		public ReportStatResult GetReportStatResult(BaseItem[] items, ReportViewType reportRowType, int topItem = 5)
-		{
-			ReportStatResult result = new ReportStatResult();
-			result = this.GetResultGenres(result, items, topItem);
-			result = this.GetResultStudios(result, items, topItem);
-			result = this.GetResultPersons(result, items, topItem);
-			result = this.GetResultProductionYears(result, items, topItem);
-			result = this.GetResulProductionLocations(result, items, topItem);
-			result = this.GetResultCommunityRatings(result, items, topItem);
-			result = this.GetResultParentalRatings(result, items, topItem);
-
-			switch (reportRowType)
-			{
-				case ReportViewType.Season:
-				case ReportViewType.Series:
-				case ReportViewType.MusicAlbum:
-				case ReportViewType.MusicArtist:
-				case ReportViewType.Game:
-					break;
-				case ReportViewType.Movie:
-				case ReportViewType.BoxSet:
-
-					break;
-				case ReportViewType.Book:
-				case ReportViewType.Episode:
-				case ReportViewType.Video:
-				case ReportViewType.MusicVideo:
-				case ReportViewType.Trailer:
-				case ReportViewType.Audio:
-				case ReportViewType.BaseItem:
-				default:
-					break;
-			}
-
-			result.Groups = result.Groups.OrderByDescending(n => n.Items.Count()).ToList();
-
-			return result;
-		}
-
-		private ReportStatResult GetResultGenres(ReportStatResult result, BaseItem[] items, int topItem = 5)
-		{
-			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderGenres"), topItem,
-							items.SelectMany(x => x.Genres)
-								.GroupBy(x => x)
-								.OrderByDescending(x => x.Count())
-								.Take(topItem)
-								.Select(x => new ReportStatItem
-								{
-									Name = x.Key,
-									Value = x.Count().ToString(),
-									Id = GetGenreID(x.Key)
-								}));
-			return result;
-
-		}
-
-		private ReportStatResult GetResultStudios(ReportStatResult result, BaseItem[] items, int topItem = 5)
-		{
-			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderStudios"), topItem,
-									items.SelectMany(x => x.Studios)
-										.GroupBy(x => x)
-										.OrderByDescending(x => x.Count())
-										.Take(topItem)
-										.Select(x => new ReportStatItem
-										{
-											Name = x.Key,
-											Value = x.Count().ToString(),
-											Id = GetStudioID(x.Key)
-										})
-					);
-
-			return result;
-
-		}
-
-		private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5)
-		{
-			List<string> t = new List<string> { PersonType.Actor, PersonType.Composer, PersonType.Director, PersonType.GuestStar, PersonType.Producer, PersonType.Writer, "Artist", "AlbumArtist" };
-			foreach (var item in t)
-			{
-				this.GetGroups(result, ReportHelper.GetServerLocalizedString("Option" + item), topItem,
-						items.SelectMany(x => _libraryManager.GetPeople(x))
-								.Where(n => n.Type == item)
-								.GroupBy(x => x.Name)
-								.OrderByDescending(x => x.Count())
-								.Take(topItem)
-								.Select(x => new ReportStatItem
-								{
-									Name = x.Key,
-									Value = x.Count().ToString(),
-									Id = GetPersonID(x.Key)
-								})
-				);
-			}
-
-			return result;
-		}
-
-		private ReportStatResult GetResultCommunityRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
-		{
-			this.GetGroups(result, ReportHelper.GetServerLocalizedString("LabelCommunityRating"), topItem,
-					   items.Where(x => x.CommunityRating != null && x.CommunityRating > 0)
-						   .GroupBy(x => x.CommunityRating)
-						   .OrderByDescending(x => x.Count())
-						   .Take(topItem)
-						   .Select(x => new ReportStatItem
-						   {
-							   Name = x.Key.ToString(),
-							   Value = x.Count().ToString()
-						   })
-			   );
-
-			return result;
-		}
-
-		private ReportStatResult GetResultParentalRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
-		{
-			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderParentalRatings"), topItem,
-					   items.Where(x => x.OfficialRating != null)
-						   .GroupBy(x => x.OfficialRating)
-						   .OrderByDescending(x => x.Count())
-						   .Take(topItem)
-						   .Select(x => new ReportStatItem
-						   {
-							   Name = x.Key.ToString(),
-							   Value = x.Count().ToString()
-						   })
-			   );
-
-			return result;
-		}
-
-
-		private ReportStatResult GetResultProductionYears(ReportStatResult result, BaseItem[] items, int topItem = 5)
-		{
-			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderYears"), topItem,
-					items.Where(x => x.ProductionYear != null && x.ProductionYear > 0)
-						.GroupBy(x => x.ProductionYear)
-						.OrderByDescending(x => x.Count())
-						.Take(topItem)
-						.Select(x => new ReportStatItem
-						{
-							Name = x.Key.ToString(),
-							Value = x.Count().ToString()
-						})
-			);
-
-			return result;
-		}
-
-		private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5)
-		{
-			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderCountries"), topItem,
-						items.OfType<IHasProductionLocations>()
-						.Where(x => x.ProductionLocations != null)
-						.SelectMany(x => x.ProductionLocations)
-						.GroupBy(x => x)
-						.OrderByDescending(x => x.Count())
-						.Take(topItem)
-						.Select(x => new ReportStatItem
-						{
-							Name = x.Key.ToString(),
-							Value = x.Count().ToString()
-						})
-			);
-
-			return result;
-		}
-
-
-		/// <summary> Gets the groups. </summary>
-		/// <param name="result"> The result. </param>
-		/// <param name="header"> The header. </param>
-		/// <param name="topItem"> The top item. </param>
-		/// <param name="top"> The top. </param>
-		private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable<ReportStatItem> top)
-		{
-			if (top.Count() > 0)
-			{
-				var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) };
-				group.Items.AddRange(top);
-				result.Groups.Add(group);
-			}
-		}
-	}
+    /// <summary> A report stat builder. </summary>
+    /// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
+    public class ReportStatBuilder : ReportBuilderBase
+    {
+        #region [Constructors]
+
+        /// <summary>
+        /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. </summary>
+        /// <param name="libraryManager"> Manager for library. </param>
+        public ReportStatBuilder(ILibraryManager libraryManager)
+            : base(libraryManager)
+        {
+        }
+
+        #endregion
+
+        #region [Public Methods]
+
+        /// <summary> Gets report stat result. </summary>
+        /// <param name="items"> The items. </param>
+        /// <param name="reportIncludeItemTypes"> List of types of the report include items. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <returns> The report stat result. </returns>
+        public ReportStatResult GetResult(BaseItem[] items, ReportIncludeItemTypes reportIncludeItemTypes, int topItem = 5)
+        {
+            ReportStatResult result = new ReportStatResult();
+            result = this.GetResultGenres(result, items, topItem);
+            result = this.GetResultStudios(result, items, topItem);
+            result = this.GetResultPersons(result, items, topItem);
+            result = this.GetResultProductionYears(result, items, topItem);
+            result = this.GetResulProductionLocations(result, items, topItem);
+            result = this.GetResultCommunityRatings(result, items, topItem);
+            result = this.GetResultParentalRatings(result, items, topItem);
+
+            switch (reportIncludeItemTypes)
+            {
+                case ReportIncludeItemTypes.Season:
+                case ReportIncludeItemTypes.Series:
+                case ReportIncludeItemTypes.MusicAlbum:
+                case ReportIncludeItemTypes.MusicArtist:
+                case ReportIncludeItemTypes.Game:
+                    break;
+                case ReportIncludeItemTypes.Movie:
+                case ReportIncludeItemTypes.BoxSet:
+
+                    break;
+                case ReportIncludeItemTypes.Book:
+                case ReportIncludeItemTypes.Episode:
+                case ReportIncludeItemTypes.Video:
+                case ReportIncludeItemTypes.MusicVideo:
+                case ReportIncludeItemTypes.Trailer:
+                case ReportIncludeItemTypes.Audio:
+                case ReportIncludeItemTypes.BaseItem:
+                default:
+                    break;
+            }
+
+            result.Groups = result.Groups.OrderByDescending(n => n.Items.Count()).ToList();
+
+            return result;
+        }
+
+        #endregion
+
+        #region [Protected Internal Methods]
+        /// <summary> Gets the headers. </summary>
+        /// <typeparam name="H"> Type of the header. </typeparam>
+        /// <param name="request"> The request. </param>
+        /// <returns> The headers. </returns>
+        /// <seealso cref="M:MediaBrowser.Api.Reports.ReportBuilderBase.GetHeaders{H}(H)"/>
+        protected internal override List<ReportHeader> GetHeaders<H>(H request)
+        {
+            throw new NotImplementedException();
+        }
+
+        #endregion
+
+        #region [Private Methods]
+
+        /// <summary> Gets the groups. </summary>
+        /// <param name="result"> The result. </param>
+        /// <param name="header"> The header. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <param name="top"> The top. </param>
+        private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable<ReportStatItem> top)
+        {
+            if (top != null && top.Count() > 0)
+            {
+                var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) };
+                group.Items.AddRange(top);
+                result.Groups.Add(group);
+            }
+        }
+
+        /// <summary> Gets resul production locations. </summary>
+        /// <param name="result"> The result. </param>
+        /// <param name="items"> The items. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <returns> The resul production locations. </returns>
+        private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5)
+        {
+            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Countries), topItem,
+                        items.OfType<IHasProductionLocations>()
+                        .Where(x => x.ProductionLocations != null)
+                        .SelectMany(x => x.ProductionLocations)
+                        .GroupBy(x => x)
+                        .OrderByDescending(x => x.Count())
+                        .Take(topItem)
+                        .Select(x => new ReportStatItem
+                        {
+                            Name = x.Key.ToString(),
+                            Value = x.Count().ToString()
+                        })
+            );
+
+            return result;
+        }
+
+        /// <summary> Gets result community ratings. </summary>
+        /// <param name="result"> The result. </param>
+        /// <param name="items"> The items. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <returns> The result community ratings. </returns>
+        private ReportStatResult GetResultCommunityRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
+        {
+            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.CommunityRating), topItem,
+                       items.Where(x => x.CommunityRating != null && x.CommunityRating > 0)
+                           .GroupBy(x => x.CommunityRating)
+                           .OrderByDescending(x => x.Count())
+                           .Take(topItem)
+                           .Select(x => new ReportStatItem
+                           {
+                               Name = x.Key.ToString(),
+                               Value = x.Count().ToString()
+                           })
+               );
+
+            return result;
+        }
+
+        /// <summary> Gets result genres. </summary>
+        /// <param name="result"> The result. </param>
+        /// <param name="items"> The items. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <returns> The result genres. </returns>
+        private ReportStatResult GetResultGenres(ReportStatResult result, BaseItem[] items, int topItem = 5)
+        {
+            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Genres), topItem,
+                            items.SelectMany(x => x.Genres)
+                                .GroupBy(x => x)
+                                .OrderByDescending(x => x.Count())
+                                .Take(topItem)
+                                .Select(x => new ReportStatItem
+                                {
+                                    Name = x.Key,
+                                    Value = x.Count().ToString(),
+                                    Id = GetGenreID(x.Key)
+                                }));
+            return result;
+
+        }
+
+        /// <summary> Gets result parental ratings. </summary>
+        /// <param name="result"> The result. </param>
+        /// <param name="items"> The items. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <returns> The result parental ratings. </returns>
+        private ReportStatResult GetResultParentalRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
+        {
+            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.ParentalRatings), topItem,
+                       items.Where(x => x.OfficialRating != null)
+                           .GroupBy(x => x.OfficialRating)
+                           .OrderByDescending(x => x.Count())
+                           .Take(topItem)
+                           .Select(x => new ReportStatItem
+                           {
+                               Name = x.Key.ToString(),
+                               Value = x.Count().ToString()
+                           })
+               );
+
+            return result;
+        }
+
+        /// <summary> Gets result persons. </summary>
+        /// <param name="result"> The result. </param>
+        /// <param name="items"> The items. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <returns> The result persons. </returns>
+        private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5)
+        {
+            List<HeaderMetadata> t = new List<HeaderMetadata> 
+            { 
+                HeaderMetadata.Actor, 
+                HeaderMetadata.Composer, 
+                HeaderMetadata.Director, 
+                HeaderMetadata.GuestStar, 
+                HeaderMetadata.Producer,
+                HeaderMetadata.Writer, 
+                HeaderMetadata.Artist, 
+                HeaderMetadata.AlbumArtist
+            };
+            foreach (var item in t)
+            {
+                var ps = items.Where(x => x.People != null && x.SupportsPeople).SelectMany(x => x.People)
+                                .Where(n => n.Type == item.ToString())
+                                .GroupBy(x => x.Name)
+                                .OrderByDescending(x => x.Count())
+                                .Take(topItem);
+                if (ps != null && ps.Count() > 0)
+                    this.GetGroups(result, GetLocalizedHeader(item), topItem,
+                            ps.Select(x => new ReportStatItem
+                                    {
+                                        Name = x.Key,
+                                        Value = x.Count().ToString(),
+                                        Id = GetPersonID(x.Key)
+                                    })
+                    );
+            }
+
+            return result;
+        }
+
+        /// <summary> Gets result production years. </summary>
+        /// <param name="result"> The result. </param>
+        /// <param name="items"> The items. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <returns> The result production years. </returns>
+        private ReportStatResult GetResultProductionYears(ReportStatResult result, BaseItem[] items, int topItem = 5)
+        {
+            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Year), topItem,
+                    items.Where(x => x.ProductionYear != null && x.ProductionYear > 0)
+                        .GroupBy(x => x.ProductionYear)
+                        .OrderByDescending(x => x.Count())
+                        .Take(topItem)
+                        .Select(x => new ReportStatItem
+                        {
+                            Name = x.Key.ToString(),
+                            Value = x.Count().ToString()
+                        })
+            );
+
+            return result;
+        }
+
+        /// <summary> Gets result studios. </summary>
+        /// <param name="result"> The result. </param>
+        /// <param name="items"> The items. </param>
+        /// <param name="topItem"> The top item. </param>
+        /// <returns> The result studios. </returns>
+        private ReportStatResult GetResultStudios(ReportStatResult result, BaseItem[] items, int topItem = 5)
+        {
+            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Studios), topItem,
+                                    items.SelectMany(x => x.Studios)
+                                        .GroupBy(x => x)
+                                        .OrderByDescending(x => x.Count())
+                                        .Take(topItem)
+                                        .Select(x => new ReportStatItem
+                                        {
+                                            Name = x.Key,
+                                            Value = x.Count().ToString(),
+                                            Id = GetStudioID(x.Key)
+                                        })
+                    );
+
+            return result;
+
+        }
+
+        #endregion
+
+    }
 }

+ 12 - 8
MediaBrowser.Api/SearchService.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Search;
@@ -171,6 +172,8 @@ namespace MediaBrowser.Api
                 ProductionYear = item.ProductionYear
             };
 
+            result.ChannelId = item.ChannelId;
+
             var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
 
             if (primaryImageTag != null)
@@ -181,24 +184,19 @@ namespace MediaBrowser.Api
             SetThumbImageInfo(result, item);
             SetBackdropImageInfo(result, item);
 
-            var episode = item as Episode;
-
-            if (episode != null)
+            var hasSeries = item as IHasSeries;
+            if (hasSeries != null)
             {
-                result.Series = episode.Series.Name;
+                result.Series = hasSeries.SeriesName;
             }
 
             var season = item as Season;
-
             if (season != null)
             {
-                result.Series = season.Series.Name;
-
                 result.EpisodeCount = season.GetRecursiveChildren(i => i is Episode).Count;
             }
 
             var series = item as Series;
-
             if (series != null)
             {
                 result.EpisodeCount = series.GetRecursiveChildren(i => i is Episode).Count;
@@ -223,6 +221,12 @@ namespace MediaBrowser.Api
                 result.Artists = song.Artists.ToArray();
             }
 
+            if (!string.IsNullOrWhiteSpace(item.ChannelId))
+            {
+                var channel = _libraryManager.GetItemById(item.ChannelId);
+                result.ChannelName = channel == null ? null : channel.Name;
+            }
+
             return result;
         }
 

+ 76 - 2
MediaBrowser.Api/StartupWizardService.cs

@@ -3,8 +3,10 @@ using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.LiveTv;
 using ServiceStack;
 using System;
 using System.Linq;
@@ -49,13 +51,15 @@ namespace MediaBrowser.Api
         private readonly IServerApplicationHost _appHost;
         private readonly IUserManager _userManager;
         private readonly IConnectManager _connectManager;
+        private readonly ILiveTvManager _liveTvManager;
 
-        public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager)
+        public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager)
         {
             _config = config;
             _appHost = appHost;
             _userManager = userManager;
             _connectManager = connectManager;
+            _liveTvManager = liveTvManager;
         }
 
         public void Post(ReportStartupWizardComplete request)
@@ -67,6 +71,8 @@ namespace MediaBrowser.Api
             _config.Configuration.EnableLibraryMetadataSubFolder = true;
             _config.Configuration.EnableUserSpecificUserViews = true;
             _config.Configuration.EnableCustomPathSubFolders = true;
+            _config.Configuration.DisableXmlSavers = true;
+            _config.Configuration.DisableStartupScan = true;
             _config.SaveConfiguration();
         }
 
@@ -82,7 +88,7 @@ namespace MediaBrowser.Api
 
         public object Get(GetStartupConfiguration request)
         {
-            return new StartupConfiguration
+            var result = new StartupConfiguration
             {
                 UICulture = _config.Configuration.UICulture,
                 EnableInternetProviders = _config.Configuration.EnableInternetProviders,
@@ -90,6 +96,22 @@ namespace MediaBrowser.Api
                 MetadataCountryCode = _config.Configuration.MetadataCountryCode,
                 PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
             };
+
+            var tvConfig = GetLiveTVConfiguration();
+
+            if (tvConfig.TunerHosts.Count > 0)
+            {
+                result.LiveTvTunerPath = tvConfig.TunerHosts[0].Url;
+                result.LiveTvTunerType = tvConfig.TunerHosts[0].Type;
+            }
+
+            if (tvConfig.ListingProviders.Count > 0)
+            {
+                result.LiveTvGuideProviderId = tvConfig.ListingProviders[0].Id;
+                result.LiveTvGuideProviderType = tvConfig.ListingProviders[0].Type;
+            }
+
+            return result;
         }
 
         public void Post(UpdateStartupConfiguration request)
@@ -100,6 +122,9 @@ namespace MediaBrowser.Api
             _config.Configuration.MetadataCountryCode = request.MetadataCountryCode;
             _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
             _config.SaveConfiguration();
+
+            var task = UpdateTuners(request);
+            Task.WaitAll(task);
         }
 
         public object Get(GetStartupUser request)
@@ -140,6 +165,51 @@ namespace MediaBrowser.Api
 
             return result;
         }
+
+        private async Task UpdateTuners(UpdateStartupConfiguration request)
+        {
+            var config = GetLiveTVConfiguration();
+            var save = false;
+
+            if (string.IsNullOrWhiteSpace(request.LiveTvTunerPath) ||
+                string.IsNullOrWhiteSpace(request.LiveTvTunerType))
+            {
+                if (config.TunerHosts.Count > 0)
+                {
+                    config.TunerHosts.Clear();
+                    save = true;
+                }
+            }
+            else
+            {
+                if (!config.TunerHosts.Any(i => string.Equals(i.Type, request.LiveTvTunerType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.Url, request.LiveTvTunerPath, StringComparison.OrdinalIgnoreCase)))
+                {
+                    // Add tuner
+                    await _liveTvManager.SaveTunerHost(new TunerHostInfo
+                    {
+                        IsEnabled = true,
+                        Type = request.LiveTvTunerType,
+                        Url = request.LiveTvTunerPath
+
+                    }).ConfigureAwait(false);
+                }
+            }
+
+            if (save)
+            {
+                SaveLiveTVConfiguration(config);
+            }
+        }
+
+        private void SaveLiveTVConfiguration(LiveTvOptions config)
+        {
+            _config.SaveConfiguration("livetv", config);
+        }
+
+        private LiveTvOptions GetLiveTVConfiguration()
+        {
+            return _config.GetConfiguration<LiveTvOptions>("livetv");
+        }
     }
 
     public class StartupConfiguration
@@ -149,6 +219,10 @@ namespace MediaBrowser.Api
         public bool SaveLocalMeta { get; set; }
         public string MetadataCountryCode { get; set; }
         public string PreferredMetadataLanguage { get; set; }
+        public string LiveTvTunerType { get; set; }
+        public string LiveTvTunerPath { get; set; }
+        public string LiveTvGuideProviderId { get; set; }
+        public string LiveTvGuideProviderType { get; set; }
     }
 
     public class StartupInfo

+ 0 - 6
MediaBrowser.Api/Sync/SyncHelper.cs

@@ -10,11 +10,6 @@ namespace MediaBrowser.Api.Sync
         {
             List<SyncJobOption> options = new List<SyncJobOption>();
 
-            if (items.Count > 1)
-            {
-                options.Add(SyncJobOption.Name);
-            }
-            
             foreach (BaseItemDto item in items)
             {
                 if (item.SupportsSync ?? false)
@@ -65,7 +60,6 @@ namespace MediaBrowser.Api.Sync
         {
             List<SyncJobOption> options = new List<SyncJobOption>();
 
-            options.Add(SyncJobOption.Name);
             options.Add(SyncJobOption.Quality);
             options.Add(SyncJobOption.Profile);
             options.Add(SyncJobOption.UnwatchedOnly);

+ 6 - 37
MediaBrowser.Api/UserLibrary/ArtistsService.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using ServiceStack;
-using System;
 using System.Collections.Generic;
 using System.Linq;
 
@@ -124,48 +123,18 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
-        protected override IEnumerable<MusicArtist> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
             if (request is GetAlbumArtists)
             {
-                return items
-                    .Where(i => !i.IsFolder)
-                    .OfType<IHasAlbumArtist>()
-                    .SelectMany(i => i.AlbumArtists)
-                    .DistinctNames()
-                    .Select(name =>
-                    {
-                        try
-                        {
-                            return LibraryManager.GetArtist(name);
-                        }
-                        catch (Exception ex)
-                        {
-                            Logger.ErrorException("Error getting artist {0}", ex, name);
-                            return null;
-                        }
-
-                    }).Where(i => i != null);
+                return LibraryManager.GetAlbumArtists(items
+                   .Where(i => !i.IsFolder)
+                   .OfType<IHasAlbumArtist>());
             }
 
-            return items
+            return LibraryManager.GetArtists(items
                 .Where(i => !i.IsFolder)
-                .OfType<IHasArtist>()
-                .SelectMany(i => i.AllArtists)
-                .DistinctNames()
-                .Select(name =>
-                {
-                    try
-                    {
-                        return LibraryManager.GetArtist(name);
-                    }
-                    catch (Exception ex)
-                    {
-                        Logger.ErrorException("Error getting artist {0}", ex, name);
-                        return null;
-                    }
-
-                }).Where(i => i != null);
+                .OfType<IHasArtist>());
         }
     }
 }

+ 45 - 25
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -48,6 +48,42 @@ namespace MediaBrowser.Api.UserLibrary
             DtoService = dtoService;
         }
 
+        protected BaseItem GetParentItem(GetItemsByName request)
+        {
+            BaseItem parentItem;
+
+            if (!string.IsNullOrWhiteSpace(request.UserId))
+            {
+                var user = UserManager.GetUserById(request.UserId);
+                parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId);
+            }
+            else
+            {
+                parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
+            }
+
+            return parentItem;
+        }
+
+        protected string GetParentItemViewType(GetItemsByName request)
+        {
+            var parent = GetParentItem(request);
+
+            var collectionFolder = parent as ICollectionFolder;
+            if (collectionFolder != null)
+            {
+                return collectionFolder.CollectionType;
+            }
+
+            var view = parent as UserView;
+            if (view != null)
+            {
+                return view.ViewType;
+            }
+
+            return null;
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -114,13 +150,13 @@ namespace MediaBrowser.Api.UserLibrary
 
             var filteredItems = FilterItems(request, extractedItems, user);
 
-            filteredItems = FilterByLibraryItems(request, filteredItems, user, libraryItems);
+            filteredItems = FilterByLibraryItems(request, filteredItems.Cast<IItemByName>(), user, libraryItems).Cast<BaseItem>();
 
-            filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending).Cast<TItemType>();
+            filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending);
 
             var ibnItemsArray = filteredItems.ToList();
 
-            IEnumerable<TItemType> ibnItems = ibnItemsArray;
+            IEnumerable<BaseItem> ibnItems = ibnItemsArray;
 
             var result = new ItemsResult
             {
@@ -141,14 +177,14 @@ namespace MediaBrowser.Api.UserLibrary
 
             }
 
-            IEnumerable<Tuple<TItemType, List<BaseItem>>> tuples;
+            IEnumerable<Tuple<BaseItem, List<BaseItem>>> tuples;
             if (dtoOptions.Fields.Contains(ItemFields.ItemCounts))
             {
-                tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, i.GetTaggedItems(libraryItems).ToList()));
+                tuples = ibnItems.Select(i => new Tuple<BaseItem, List<BaseItem>>(i, ((IItemByName)i).GetTaggedItems(libraryItems).ToList()));
             }
             else
             {
-                tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, new List<BaseItem>()));
+                tuples = ibnItems.Select(i => new Tuple<BaseItem, List<BaseItem>>(i, new List<BaseItem>()));
             }
 
             var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user));
@@ -180,7 +216,7 @@ namespace MediaBrowser.Api.UserLibrary
             return options.Fields.Contains(ItemFields.ItemCounts);
         }
 
-        private IEnumerable<TItemType> FilterByLibraryItems(GetItemsByName request, IEnumerable<TItemType> items, User user, IEnumerable<BaseItem> libraryItems)
+        private IEnumerable<IItemByName> FilterByLibraryItems(GetItemsByName request, IEnumerable<IItemByName> items, User user, IEnumerable<BaseItem> libraryItems)
         {
             var filters = request.GetFilters().ToList();
 
@@ -211,7 +247,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="items">The items.</param>
         /// <param name="user">The user.</param>
         /// <returns>IEnumerable{`0}.</returns>
-        private IEnumerable<TItemType> FilterItems(GetItemsByName request, IEnumerable<TItemType> items, User user)
+        private IEnumerable<BaseItem> FilterItems(GetItemsByName request, IEnumerable<BaseItem> items, User user)
         {
             if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater))
             {
@@ -375,7 +411,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{Task{`0}}.</returns>
-        protected abstract IEnumerable<TItemType> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items);
+        protected abstract IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items);
     }
 
     /// <summary>
@@ -383,22 +419,6 @@ namespace MediaBrowser.Api.UserLibrary
     /// </summary>
     public class GetItemsByName : BaseItemsRequest, IReturn<ItemsResult>
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string UserId { get; set; }
-
-        [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameStartsWithOrGreater { get; set; }
-
-        [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameStartsWith { get; set; }
-
-        [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is sorted less than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameLessThan { get; set; }
-
         public GetItemsByName()
         {
             Recursive = true;

+ 243 - 0
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -14,6 +14,97 @@ namespace MediaBrowser.Api.UserLibrary
             EnableImages = true;
         }
 
+        /// <summary>
+        /// Gets or sets the max offical rating.
+        /// </summary>
+        /// <value>The max offical rating.</value>
+        [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string MaxOfficialRating { get; set; }
+
+        [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? HasThemeSong { get; set; }
+
+        [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? HasThemeVideo { get; set; }
+
+        [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? HasSubtitles { get; set; }
+
+        [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? HasSpecialFeature { get; set; }
+
+        [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? HasTrailer { get; set; }
+
+        [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string AdjacentTo { get; set; }
+
+        [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? MinIndexNumber { get; set; }
+
+        [ApiMember(Name = "MinPlayers", Description = "Optional filter by minimum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? MinPlayers { get; set; }
+
+        [ApiMember(Name = "MaxPlayers", Description = "Optional filter by maximum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? MaxPlayers { get; set; }
+
+        [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? ParentIndexNumber { get; set; }
+
+        [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? HasParentalRating { get; set; }
+
+        [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsHD { get; set; }
+
+        [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string LocationTypes { get; set; }
+
+        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string ExcludeLocationTypes { get; set; }
+
+        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsMissing { get; set; }
+
+        [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsUnaired { get; set; }
+
+        [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsVirtualUnaired { get; set; }
+
+        [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public double? MinCommunityRating { get; set; }
+
+        [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public double? MinCriticRating { get; set; }
+
+        [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? AiredDuringSeason { get; set; }
+
+        [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MinPremiereDate { get; set; }
+
+        [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MaxPremiereDate { get; set; }
+
+        [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? HasOverview { get; set; }
+
+        [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? HasImdbId { get; set; }
+
+        [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? HasTmdbId { get; set; }
+
+        [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? HasTvdbId { get; set; }
+
+        [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsYearMismatched { get; set; }
+
+        [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsInBoxSet { get; set; }
+        
         /// <summary>
         /// Skips over a given number of items within the results. Use for paging.
         /// </summary>
@@ -130,6 +221,121 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string EnableImageTypes { get; set; }
 
+        /// <summary>
+        /// Limit results to items containing a specific person
+        /// </summary>
+        /// <value>The person.</value>
+        [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Person { get; set; }
+
+        [ApiMember(Name = "PersonIds", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string PersonIds { get; set; }
+
+        /// <summary>
+        /// If the Person filter is used, this can also be used to restrict to a specific person type
+        /// </summary>
+        /// <value>The type of the person.</value>
+        [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string PersonTypes { get; set; }
+
+        /// <summary>
+        /// Limit results to items containing specific studios
+        /// </summary>
+        /// <value>The studios.</value>
+        [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Studios { get; set; }
+
+        [ApiMember(Name = "StudioIds", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string StudioIds { get; set; }
+
+        /// <summary>
+        /// Gets or sets the studios.
+        /// </summary>
+        /// <value>The studios.</value>
+        [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Artists { get; set; }
+
+        [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string ArtistIds { get; set; }
+
+        [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Albums { get; set; }
+
+        /// <summary>
+        /// Gets or sets the item ids.
+        /// </summary>
+        /// <value>The item ids.</value>
+        [ApiMember(Name = "Ids", Description = "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Ids { get; set; }
+
+        /// <summary>
+        /// Gets or sets the video types.
+        /// </summary>
+        /// <value>The video types.</value>
+        [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string VideoTypes { get; set; }
+
+        /// <summary>
+        /// Gets or sets the air days.
+        /// </summary>
+        /// <value>The air days.</value>
+        [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string AirDays { get; set; }
+
+        /// <summary>
+        /// Gets or sets the user id.
+        /// </summary>
+        /// <value>The user id.</value>
+        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string UserId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the min offical rating.
+        /// </summary>
+        /// <value>The min offical rating.</value>
+        [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string MinOfficialRating { get; set; }
+
+        [ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? IsLocked { get; set; }
+
+        [ApiMember(Name = "IsUnidentified", Description = "Optional filter by items that are unidentified by internet metadata providers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? IsUnidentified { get; set; }
+
+        [ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? IsPlaceHolder { get; set; }
+
+        [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public bool? HasOfficialRating { get; set; }
+
+        [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? CollapseBoxSetItems { get; set; }
+        /// <summary>
+        /// Gets or sets the video formats.
+        /// </summary>
+        /// <value>The video formats.</value>
+        [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? Is3D { get; set; }
+
+        /// <summary>
+        /// Gets or sets the series status.
+        /// </summary>
+        /// <value>The series status.</value>
+        [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string SeriesStatus { get; set; }
+
+        [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string NameStartsWithOrGreater { get; set; }
+
+        [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string NameStartsWith { get; set; }
+
+        [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string NameLessThan { get; set; }
+
+        [ApiMember(Name = "AlbumArtistStartsWithOrGreater", Description = "Optional filter by items whose album artist is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string AlbumArtistStartsWithOrGreater { get; set; }
+
         public string[] GetGenres()
         {
             return (Genres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
@@ -164,6 +370,43 @@ namespace MediaBrowser.Api.UserLibrary
         {
             return (Years ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
         }
+
+        public string[] GetStudios()
+        {
+            return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
+        public string[] GetStudioIds()
+        {
+            return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
+        public string[] GetPersonTypes()
+        {
+            return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
+        public string[] GetPersonIds()
+        {
+            return (PersonIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
+        public string[] GetItemIds()
+        {
+            return (Ids ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
+        public VideoType[] GetVideoTypes()
+        {
+            var val = VideoTypes;
+
+            if (string.IsNullOrEmpty(val))
+            {
+                return new VideoType[] { };
+            }
+
+            return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
+        }
         
         /// <summary>
         /// Gets the filters.

+ 15 - 5
MediaBrowser.Api/UserLibrary/GameGenresService.cs

@@ -99,14 +99,24 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
-        protected override IEnumerable<GameGenre> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
-            var itemsList = items.Where(i => i.Genres != null).ToList();
-
-            return itemsList
+            return items
                 .SelectMany(i => i.Genres)
                 .DistinctNames()
-                .Select(name => LibraryManager.GetGameGenre(name));
+                .Select(name =>
+                {
+                    try
+                    {
+                        return LibraryManager.GetGameGenre(name);
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.ErrorException("Error getting genre {0}", ex, name);
+                        return null;
+                    }
+                })
+                .Where(i => i != null);
         }
     }
 }

+ 32 - 3
MediaBrowser.Api/UserLibrary/GenresService.cs

@@ -1,11 +1,10 @@
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Entities;
 using ServiceStack;
 using System;
 using System.Collections.Generic;
@@ -104,8 +103,38 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
-        protected override IEnumerable<Genre> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
+            var viewType = GetParentItemViewType(request);
+
+            if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos))
+            {
+                return items
+                    .SelectMany(i => i.Genres)
+                    .DistinctNames()
+                    .Select(name => LibraryManager.GetMusicGenre(name));
+            }
+
+            if (string.Equals(viewType, CollectionType.Games))
+            {
+                return items
+                    .SelectMany(i => i.Genres)
+                    .DistinctNames()
+                    .Select(name =>
+                    {
+                        try
+                        {
+                            return LibraryManager.GetGameGenre(name);
+                        }
+                        catch (Exception ex)
+                        {
+                            Logger.ErrorException("Error getting genre {0}", ex, name);
+                            return null;
+                        }
+                    })
+                    .Where(i => i != null);
+            }
+
             return items
                 .SelectMany(i => i.Genres)
                 .DistinctNames()

+ 10 - 245
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -2,7 +2,6 @@
 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.Localization;
@@ -25,249 +24,6 @@ namespace MediaBrowser.Api.UserLibrary
     [Route("/Users/{UserId}/Items", "GET", Summary = "Gets items based on a query.")]
     public class GetItems : BaseItemsRequest, IReturn<ItemsResult>
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string UserId { get; set; }
-
-        /// <summary>
-        /// Limit results to items containing a specific person
-        /// </summary>
-        /// <value>The person.</value>
-        [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string Person { get; set; }
-
-        [ApiMember(Name = "PersonIds", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string PersonIds { get; set; }
-
-        /// <summary>
-        /// If the Person filter is used, this can also be used to restrict to a specific person type
-        /// </summary>
-        /// <value>The type of the person.</value>
-        [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string PersonTypes { get; set; }
-
-        /// <summary>
-        /// Limit results to items containing specific studios
-        /// </summary>
-        /// <value>The studios.</value>
-        [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Studios { get; set; }
-
-        [ApiMember(Name = "StudioIds", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string StudioIds { get; set; }
-
-        /// <summary>
-        /// Gets or sets the studios.
-        /// </summary>
-        /// <value>The studios.</value>
-        [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Artists { get; set; }
-
-        [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string ArtistIds { get; set; }
-
-        [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Albums { get; set; }
-
-        /// <summary>
-        /// Gets or sets the item ids.
-        /// </summary>
-        /// <value>The item ids.</value>
-        [ApiMember(Name = "Ids", Description = "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Ids { get; set; }
-
-        /// <summary>
-        /// Gets or sets the video types.
-        /// </summary>
-        /// <value>The video types.</value>
-        [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string VideoTypes { get; set; }
-
-        /// <summary>
-        /// Gets or sets the video formats.
-        /// </summary>
-        /// <value>The video formats.</value>
-        [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? Is3D { get; set; }
-
-        /// <summary>
-        /// Gets or sets the series status.
-        /// </summary>
-        /// <value>The series status.</value>
-        [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string SeriesStatus { get; set; }
-
-        [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameStartsWithOrGreater { get; set; }
-
-        [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameStartsWith { get; set; }
-
-        [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string NameLessThan { get; set; }
-
-        [ApiMember(Name = "AlbumArtistStartsWithOrGreater", Description = "Optional filter by items whose album artist is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string AlbumArtistStartsWithOrGreater { get; set; }
-
-        /// <summary>
-        /// Gets or sets the air days.
-        /// </summary>
-        /// <value>The air days.</value>
-        [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string AirDays { get; set; }
-
-        /// <summary>
-        /// Gets or sets the min offical rating.
-        /// </summary>
-        /// <value>The min offical rating.</value>
-        [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string MinOfficialRating { get; set; }
-
-        /// <summary>
-        /// Gets or sets the max offical rating.
-        /// </summary>
-        /// <value>The max offical rating.</value>
-        [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string MaxOfficialRating { get; set; }
-
-        [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasThemeSong { get; set; }
-
-        [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasThemeVideo { get; set; }
-
-        [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasSubtitles { get; set; }
-
-        [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasSpecialFeature { get; set; }
-
-        [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasTrailer { get; set; }
-
-        [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string AdjacentTo { get; set; }
-
-        [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? MinIndexNumber { get; set; }
-
-        [ApiMember(Name = "MinPlayers", Description = "Optional filter by minimum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? MinPlayers { get; set; }
-
-        [ApiMember(Name = "MaxPlayers", Description = "Optional filter by maximum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? MaxPlayers { get; set; }
-
-        [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? ParentIndexNumber { get; set; }
-
-        [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasParentalRating { get; set; }
-
-        [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsHD { get; set; }
-
-        [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string LocationTypes { get; set; }
-
-        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string ExcludeLocationTypes { get; set; }
-
-        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsMissing { get; set; }
-
-        [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsUnaired { get; set; }
-
-        [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsVirtualUnaired { get; set; }
-
-        [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public double? MinCommunityRating { get; set; }
-
-        [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public double? MinCriticRating { get; set; }
-
-        [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? AiredDuringSeason { get; set; }
-
-        [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string MinPremiereDate { get; set; }
-
-        [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string MaxPremiereDate { get; set; }
-
-        [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasOverview { get; set; }
-
-        [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasImdbId { get; set; }
-
-        [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasTmdbId { get; set; }
-
-        [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? HasTvdbId { get; set; }
-
-        [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsYearMismatched { get; set; }
-
-        [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsInBoxSet { get; set; }
-
-        [ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? IsLocked { get; set; }
-
-        [ApiMember(Name = "IsUnidentified", Description = "Optional filter by items that are unidentified by internet metadata providers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? IsUnidentified { get; set; }
-
-        [ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? IsPlaceHolder { get; set; }
-
-        [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasOfficialRating { get; set; }
-
-        [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? CollapseBoxSetItems { get; set; }
-
-        public string[] GetStudios()
-        {
-            return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public string[] GetStudioIds()
-        {
-            return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public string[] GetPersonTypes()
-        {
-            return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public string[] GetPersonIds()
-        {
-            return (PersonIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public string[] GetItemIds()
-        {
-            return (Ids ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public VideoType[] GetVideoTypes()
-        {
-            var val = VideoTypes;
-
-            if (string.IsNullOrEmpty(val))
-            {
-                return new VideoType[] { };
-            }
-
-            return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
-        }
     }
 
     /// <summary>
@@ -361,7 +117,16 @@ namespace MediaBrowser.Api.UserLibrary
             if (!string.IsNullOrEmpty(request.Ids))
             {
                 request.Recursive = true;
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+                var query = GetItemsQuery(request, user);
+                var result = await ((Folder)item).GetItems(query).ConfigureAwait(false);
+
+                if (string.IsNullOrWhiteSpace(request.SortBy))
+                {
+                    var ids = query.ItemIds.ToList();
+
+                    // Try to preserve order
+                    result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
+                }
 
                 return new Tuple<QueryResult<BaseItem>, bool>(result, true);
             }

+ 2 - 4
MediaBrowser.Api/UserLibrary/MusicGenresService.cs

@@ -99,11 +99,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
-        protected override IEnumerable<MusicGenre> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
-            var itemsList = items.ToList();
-
-            return itemsList
+            return items
                 .SelectMany(i => i.Genres)
                 .DistinctNames()
                 .Select(name => LibraryManager.GetMusicGenre(name));

+ 1 - 7
MediaBrowser.Api/UserLibrary/PersonsService.cs

@@ -16,12 +16,6 @@ namespace MediaBrowser.Api.UserLibrary
     [Route("/Persons", "GET", Summary = "Gets all persons from a given item, folder, or the entire library")]
     public class GetPersons : GetItemsByName
     {
-        /// <summary>
-        /// Gets or sets the person types.
-        /// </summary>
-        /// <value>The person types.</value>
-        [ApiMember(Name = "PersonTypes", Description = "Optional filter by person type. Accepts multiple, comma-delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string PersonTypes { get; set; }
     }
 
     /// <summary>
@@ -114,7 +108,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
-        protected override IEnumerable<Person> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
             var inputPersonTypes = ((GetPersons)request).PersonTypes;
             var personTypes = string.IsNullOrEmpty(inputPersonTypes) ? new string[] { } : inputPersonTypes.Split(',');

+ 5 - 1
MediaBrowser.Api/UserLibrary/PlaystateService.cs

@@ -185,6 +185,9 @@ namespace MediaBrowser.Api.UserLibrary
 
         [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string PlaySessionId { get; set; }
+
+        [ApiMember(Name = "RepeatMode", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public RepeatMode RepeatMode { get; set; }
     }
 
     /// <summary>
@@ -325,7 +328,8 @@ namespace MediaBrowser.Api.UserLibrary
                 VolumeLevel = request.VolumeLevel,
                 PlayMethod = request.PlayMethod,
                 PlaySessionId = request.PlaySessionId,
-                LiveStreamId = request.LiveStreamId
+                LiveStreamId = request.LiveStreamId,
+                RepeatMode = request.RepeatMode
             });
         }
 

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

@@ -103,7 +103,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
-        protected override IEnumerable<Studio> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
             var itemsList = items.Where(i => i.Studios != null).ToList();
 

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

@@ -89,7 +89,7 @@ namespace MediaBrowser.Api.UserLibrary
             var views = user.RootFolder
                 .GetChildren(user, true)
                 .OfType<ICollectionFolder>()
-                .Where(i => IsEligibleForSpecialView(i))
+                .Where(IsEligibleForSpecialView)
                 .ToList();
 
             var list = views

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

@@ -103,7 +103,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
-        protected override IEnumerable<Year> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
             var itemsList = items.Where(i => i.ProductionYear != null).ToList();
 

+ 16 - 5
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -68,6 +68,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
             ServicePointManager.Expect100Continue = false;
+
+            // Trakt requests sometimes fail without this
+            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
         }
 
         /// <summary>
@@ -124,7 +127,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
         private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
         {
-            var request = WebRequest.Create(options.Url);
+            var request = CreateWebRequest(options.Url);
             var httpWebRequest = request as HttpWebRequest;
 
             if (httpWebRequest != null)
@@ -432,7 +435,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
                     var httpResponse = (HttpWebResponse)response;
 
-                    EnsureSuccessStatusCode(httpResponse, options);
+                    EnsureSuccessStatusCode(client, httpResponse, options);
 
                     options.CancellationToken.ThrowIfCancellationRequested();
 
@@ -443,7 +446,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                 {
                     var httpResponse = (HttpWebResponse)response;
 
-                    EnsureSuccessStatusCode(httpResponse, options);
+                    EnsureSuccessStatusCode(client, httpResponse, options);
 
                     options.CancellationToken.ThrowIfCancellationRequested();
 
@@ -629,7 +632,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                 {
                     var httpResponse = (HttpWebResponse)response;
 
-                    EnsureSuccessStatusCode(httpResponse, options);
+                    var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression);
+                    EnsureSuccessStatusCode(client, httpResponse, options);
 
                     options.CancellationToken.ThrowIfCancellationRequested();
 
@@ -803,13 +807,20 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
             return exception;
         }
 
-        private void EnsureSuccessStatusCode(HttpWebResponse response, HttpRequestOptions options)
+        private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options)
         {
             var statusCode = response.StatusCode;
+
             var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299;
 
             if (!isSuccessful)
             {
+                if ((int) statusCode == 429)
+                {
+                    client.LastTimeout = DateTime.UtcNow;
+                }
+
+                if (statusCode == HttpStatusCode.RequestEntityTooLarge)
                 if (options.LogErrorResponseBody)
                 {
                     try

+ 2 - 2
MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs

@@ -234,10 +234,10 @@ namespace MediaBrowser.Common.Implementations.IO
         {
             if (_supportsAsyncFileStreams && isAsync)
             {
-                return new FileStream(path, mode, access, share, 4096, true);
+                return new FileStream(path, mode, access, share, StreamDefaults.DefaultFileStreamBufferSize, true);
             }
 
-            return new FileStream(path, mode, access, share);
+            return new FileStream(path, mode, access, share, StreamDefaults.DefaultFileStreamBufferSize);
         }
 
         /// <summary>

+ 2 - 2
MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -312,7 +312,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
 
                 trigger.Triggered -= trigger_Triggered;
                 trigger.Triggered += trigger_Triggered;
-                trigger.Start(isApplicationStartup);
+                trigger.Start(LastExecutionResult, isApplicationStartup);
             }
         }
 
@@ -340,7 +340,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
 
             await Task.Delay(1000).ConfigureAwait(false);
 
-            trigger.Start(false);
+            trigger.Start(LastExecutionResult, false);
         }
 
         private Task _currentTask;

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

@@ -45,9 +45,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
             // Until we can vary these default triggers per server and MBT, we need something that makes sense for both
             return new ITaskTrigger[] { 
             
-                // At startup
-                new StartupTrigger {DelayMs = 60000},
-
                 // Every so often
                 new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
             };

+ 0 - 3
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs

@@ -42,9 +42,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
             // Until we can vary these default triggers per server and MBT, we need something that makes sense for both
             return new ITaskTrigger[] { 
             
-                // At startup
-                new StartupTrigger {DelayMs = 30000},
-
                 // Every so often
                 new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
             };

+ 1 - 1
MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs

@@ -61,7 +61,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
 
         private Stream OpenFile(string path)
         {
-            return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
+            return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072);
         }
 
         /// <summary>

+ 15 - 4
MediaBrowser.Common.Implementations/Updates/InstallationManager.cs

@@ -149,10 +149,12 @@ namespace MediaBrowser.Common.Implementations.Updates
         /// Gets all available packages.
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
+        /// <param name="withRegistration">if set to <c>true</c> [with registration].</param>
         /// <param name="packageType">Type of the package.</param>
         /// <param name="applicationVersion">The application version.</param>
         /// <returns>Task{List{PackageInfo}}.</returns>
         public async Task<IEnumerable<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
+            bool withRegistration = true,
             PackageType? packageType = null,
             Version applicationVersion = null)
         {
@@ -163,13 +165,22 @@ namespace MediaBrowser.Common.Implementations.Updates
                 { "systemid", _applicationHost.SystemId }
             };
 
-            using (var json = await _httpClient.Post(MbAdmin.HttpsUrl + "service/package/retrieveall", data, cancellationToken).ConfigureAwait(false))
+            if (withRegistration)
             {
-                cancellationToken.ThrowIfCancellationRequested();
+                using (var json = await _httpClient.Post(MbAdmin.HttpsUrl + "service/package/retrieveall", data, cancellationToken).ConfigureAwait(false))
+                {
+                    cancellationToken.ThrowIfCancellationRequested();
 
-                var packages = _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(json).ToList();
+                    var packages = _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(json).ToList();
+
+                    return FilterPackages(packages, packageType, applicationVersion);
+                }
+            }
+            else
+            {
+                var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
 
-                return FilterPackages(packages, packageType, applicationVersion);
+                return FilterPackages(packages.ToList(), packageType, applicationVersion);
             }
         }
 

+ 2 - 2
MediaBrowser.Common/IO/StreamDefaults.cs

@@ -9,11 +9,11 @@ namespace MediaBrowser.Common.IO
         /// <summary>
         /// The default copy to buffer size
         /// </summary>
-        public const int DefaultCopyToBufferSize = 81920;
+        public const int DefaultCopyToBufferSize = 262144;
 
         /// <summary>
         /// The default file stream buffer size
         /// </summary>
-        public const int DefaultFileStreamBufferSize = 4096;
+        public const int DefaultFileStreamBufferSize = 262144;
     }
 }

+ 5 - 3
MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs

@@ -1,6 +1,7 @@
-using System;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Tasks;
+using System;
 using System.Threading;
-using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Common.ScheduledTasks
 {
@@ -32,8 +33,9 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// <summary>
         /// Stars waiting for the trigger action
         /// </summary>
+        /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public void Start(bool isApplicationStartup)
+        public void Start(TaskResult lastResult, bool isApplicationStartup)
         {
             DisposeTimer();
 

+ 5 - 3
MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs

@@ -1,5 +1,6 @@
-using System;
-using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Tasks;
+using System;
 
 namespace MediaBrowser.Common.ScheduledTasks
 {
@@ -16,8 +17,9 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// <summary>
         /// Stars waiting for the trigger action
         /// </summary>
+        /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        void Start(bool isApplicationStartup);
+        void Start(TaskResult lastResult, bool isApplicationStartup);
 
         /// <summary>
         /// Stops waiting for the trigger action

+ 33 - 4
MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs

@@ -1,6 +1,7 @@
-using System;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Tasks;
+using System;
 using System.Threading;
-using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Common.ScheduledTasks
 {
@@ -29,15 +30,43 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// </value>
         public TaskExecutionOptions TaskOptions { get; set; }
 
+        /// <summary>
+        /// Gets or sets the first run delay.
+        /// </summary>
+        /// <value>The first run delay.</value>
+        public TimeSpan FirstRunDelay { get; set; }
+
+        public IntervalTrigger()
+        {
+            FirstRunDelay = TimeSpan.FromHours(1);
+        }
+
         /// <summary>
         /// Stars waiting for the trigger action
         /// </summary>
+        /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public void Start(bool isApplicationStartup)
+        public void Start(TaskResult lastResult, bool isApplicationStartup)
         {
             DisposeTimer();
 
-            Timer = new Timer(state => OnTriggered(), null, Interval, TimeSpan.FromMilliseconds(-1));
+            var triggerDate = lastResult != null ?
+                lastResult.EndTimeUtc.Add(Interval) :
+                DateTime.UtcNow.Add(FirstRunDelay);
+
+            if (DateTime.UtcNow > triggerDate)
+            {
+                if (isApplicationStartup)
+                {
+                    triggerDate = DateTime.UtcNow.AddMinutes(5);
+                }
+                else
+                {
+                    triggerDate = DateTime.UtcNow.Add(Interval);
+                }
+            }
+
+            Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.UtcNow, TimeSpan.FromMilliseconds(-1));
         }
 
         /// <summary>

+ 5 - 3
MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs

@@ -1,6 +1,7 @@
-using System;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Tasks;
+using System;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Common.ScheduledTasks
 {
@@ -27,8 +28,9 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// <summary>
         /// Stars waiting for the trigger action
         /// </summary>
+        /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public async void Start(bool isApplicationStartup)
+        public async void Start(TaskResult lastResult, bool isApplicationStartup)
         {
             if (isApplicationStartup)
             {

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

@@ -1,8 +1,8 @@
-using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Tasks;
 using Microsoft.Win32;
 using System;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Common.ScheduledTasks
 {
@@ -28,8 +28,9 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// <summary>
         /// Stars waiting for the trigger action
         /// </summary>
+        /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public void Start(bool isApplicationStartup)
+        public void Start(TaskResult lastResult, bool isApplicationStartup)
         {
             switch (SystemEvent)
             {

+ 3 - 1
MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Threading;
 using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Tasks;
 
 namespace MediaBrowser.Common.ScheduledTasks
 {
@@ -38,8 +39,9 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// <summary>
         /// Stars waiting for the trigger action
         /// </summary>
+        /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public void Start(bool isApplicationStartup)
+        public void Start(TaskResult lastResult, bool isApplicationStartup)
         {
             DisposeTimer();
 

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

@@ -45,10 +45,12 @@ namespace MediaBrowser.Common.Updates
         /// Gets all available packages.
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
+        /// <param name="withRegistration">if set to <c>true</c> [with registration].</param>
         /// <param name="packageType">Type of the package.</param>
         /// <param name="applicationVersion">The application version.</param>
         /// <returns>Task{List{PackageInfo}}.</returns>
         Task<IEnumerable<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
+            bool withRegistration = true,
                                                                                   PackageType? packageType = null,
                                                                                   Version applicationVersion = null);
 

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

@@ -121,10 +121,9 @@ namespace MediaBrowser.Controller.Channels
         /// <summary>
         /// Gets the channel folder.
         /// </summary>
-        /// <param name="userId">The user identifier.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>BaseItemDto.</returns>
-        Task<Folder> GetInternalChannelFolder(string userId, CancellationToken cancellationToken);
+        Task<Folder> GetInternalChannelFolder(CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the channel folder.

+ 10 - 0
MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs

@@ -0,0 +1,10 @@
+using System;
+
+namespace MediaBrowser.Controller.Dlna
+{
+    public interface IDeviceDiscovery
+    {
+        event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
+        event EventHandler<SsdpMessageEventArgs> DeviceLeft;
+    }
+}

+ 1 - 3
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -81,13 +81,11 @@ namespace MediaBrowser.Controller.Dto
         /// <summary>
         /// Gets the item by name dto.
         /// </summary>
-        /// <typeparam name="T"></typeparam>
         /// <param name="item">The item.</param>
         /// <param name="options">The options.</param>
         /// <param name="taggedItems">The tagged items.</param>
         /// <param name="user">The user.</param>
         /// <returns>BaseItemDto.</returns>
-        BaseItemDto GetItemByNameDto<T>(T item, DtoOptions options, List<BaseItem> taggedItems, User user = null)
-            where T : BaseItem, IItemByName;
+        BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null);
     }
 }

+ 6 - 0
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -54,6 +54,12 @@ namespace MediaBrowser.Controller.Entities.Audio
             get { return AlbumArtists.FirstOrDefault(); }
         }
 
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get { return false; }
+        }
+
         public List<string> AlbumArtists { get; set; }
 
         /// <summary>

+ 23 - 1
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Entities
         public virtual string Path { get; set; }
 
         [IgnoreDataMember]
-        protected internal bool IsOffline { get; set; }
+        public bool IsOffline { get; set; }
 
         /// <summary>
         /// Returns the folder containing the item.
@@ -419,6 +419,10 @@ namespace MediaBrowser.Controller.Entities
 
                 return _sortName ?? (_sortName = CreateSortName());
             }
+            set
+            {
+                _sortName = value;
+            }
         }
 
         public string GetInternalMetadataPath()
@@ -485,6 +489,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the parent.
         /// </summary>
         /// <value>The parent.</value>
+        [IgnoreDataMember]
         public Folder Parent
         {
             get
@@ -1115,6 +1120,23 @@ namespace MediaBrowser.Controller.Entities
             return value.Value <= maxAllowedRating.Value;
         }
 
+        public int? GetParentalRatingValue()
+        {
+            var rating = CustomRatingForComparison;
+
+            if (string.IsNullOrWhiteSpace(rating))
+            {
+                rating = OfficialRatingForComparison;
+            }
+
+            if (string.IsNullOrWhiteSpace(rating))
+            {
+                return null;
+            }
+
+            return LocalizationManager.GetRatingLevel(rating);
+        }
+
         private bool IsVisibleViaTags(User user)
         {
             var hasTags = this as IHasTags;

+ 9 - 0
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -35,6 +35,15 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        [IgnoreDataMember]
+        protected override bool SupportsShortcutChildren
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         public override bool CanDelete()
         {
             return false;

+ 35 - 31
MediaBrowser.Controller/Entities/Folder.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -14,7 +13,6 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -50,7 +48,7 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         public virtual bool IsPreSorted
         {
-            get { return false; }
+            get { return ConfigurationManager.Configuration.EnableWindowsShortcuts; }
         }
 
         /// <summary>
@@ -122,7 +120,7 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         protected virtual bool SupportsShortcutChildren
         {
-            get { return true; }
+            get { return false; }
         }
 
         /// <summary>
@@ -176,7 +174,7 @@ namespace MediaBrowser.Controller.Entities
         protected void AddChildInternal(BaseItem child)
         {
             var actualChildren = ActualChildren;
-            
+
             lock (_childrenSyncLock)
             {
                 var newChildren = actualChildren.ToList();
@@ -1070,7 +1068,7 @@ namespace MediaBrowser.Controller.Entities
         {
             var changesFound = false;
 
-            if (SupportsShortcutChildren && LocationType == LocationType.FileSystem)
+            if (LocationType == LocationType.FileSystem)
             {
                 if (RefreshLinkedChildren(fileSystemChildren))
                 {
@@ -1092,37 +1090,43 @@ namespace MediaBrowser.Controller.Entities
             var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
             var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
 
-            var newShortcutLinks = fileSystemChildren
-                .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName))
-                .Select(i =>
-                {
-                    try
+            List<LinkedChild> newShortcutLinks;
+
+            if (SupportsShortcutChildren)
+            {
+                newShortcutLinks = fileSystemChildren
+                    .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName))
+                    .Select(i =>
                     {
-                        Logger.Debug("Found shortcut at {0}", i.FullName);
+                        try
+                        {
+                            Logger.Debug("Found shortcut at {0}", i.FullName);
 
-                        var resolvedPath = FileSystem.ResolveShortcut(i.FullName);
+                            var resolvedPath = FileSystem.ResolveShortcut(i.FullName);
 
-                        if (!string.IsNullOrEmpty(resolvedPath))
-                        {
-                            return new LinkedChild
+                            if (!string.IsNullOrEmpty(resolvedPath))
                             {
-                                Path = resolvedPath,
-                                Type = LinkedChildType.Shortcut
-                            };
-                        }
+                                return new LinkedChild
+                                {
+                                    Path = resolvedPath,
+                                    Type = LinkedChildType.Shortcut
+                                };
+                            }
 
-                        Logger.Error("Error resolving shortcut {0}", i.FullName);
+                            Logger.Error("Error resolving shortcut {0}", i.FullName);
 
-                        return null;
-                    }
-                    catch (IOException ex)
-                    {
-                        Logger.ErrorException("Error resolving shortcut {0}", ex, i.FullName);
-                        return null;
-                    }
-                })
-                .Where(i => i != null)
-                .ToList();
+                            return null;
+                        }
+                        catch (IOException ex)
+                        {
+                            Logger.ErrorException("Error resolving shortcut {0}", ex, i.FullName);
+                            return null;
+                        }
+                    })
+                    .Where(i => i != null)
+                    .ToList();
+            }
+            else { newShortcutLinks = new List<LinkedChild>(); }
 
             if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer()))
             {

+ 14 - 0
MediaBrowser.Controller/Entities/ICollectionFolder.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -14,4 +15,17 @@ namespace MediaBrowser.Controller.Entities
         Guid Id { get; }
         IEnumerable<string> PhysicalLocations { get; }
     }
+
+    public static class CollectionFolderExtensions
+    {
+        public static string GetViewType(this ICollectionFolder folder, User user)
+        {
+            if (user.Configuration.PlainFolderViews.Contains(folder.Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
+            {
+                return null;
+            }
+
+            return folder.CollectionType;
+        }
+    }
 }

+ 1 - 0
MediaBrowser.Controller/Entities/IHasProgramAttributes.cs

@@ -11,6 +11,7 @@ namespace MediaBrowser.Controller.Entities
         bool IsKids { get; set; }
         bool IsRepeat { get; set; }
         bool? IsHD { get; set; }
+        bool IsSeries { get; set; }
         bool IsLive { get; set; }
         bool IsPremiere { get; set; }
         ProgramAudio? Audio { get; set; }

+ 7 - 2
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Entities;
 using System;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -40,6 +40,7 @@ namespace MediaBrowser.Controller.Entities
         public string NameStartsWithOrGreater { get; set; }
         public string NameStartsWith { get; set; }
         public string NameLessThan { get; set; }
+        public string NameContains { get; set; }
 
         public string Person { get; set; }
         public string[] PersonIds { get; set; }
@@ -93,7 +94,11 @@ namespace MediaBrowser.Controller.Entities
         public string[] ChannelIds { get; set; }
 
         internal List<Guid> ItemIdsFromPersonFilters { get; set; }
+        public int? MaxParentalRating { get; set; }
 
+        public bool? IsCurrentSchema { get; set; }
+        public bool? HasDeadParentId { get; set; }
+    
         public InternalItemsQuery()
         {
             Tags = new string[] { };

+ 9 - 0
MediaBrowser.Controller/Entities/Movies/BoxSet.cs

@@ -74,6 +74,15 @@ namespace MediaBrowser.Controller.Entities.Movies
             }
         }
 
+        [IgnoreDataMember]
+        protected override bool SupportsShortcutChildren
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         public override bool IsAuthorizedToDelete(User user)
         {
             return true;

+ 10 - 3
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -100,16 +100,23 @@ namespace MediaBrowser.Controller.Entities.Movies
         /// <returns>System.String.</returns>
         protected override string CreateUserDataKey()
         {
-            var key = this.GetProviderId(MetadataProviders.Tmdb);
+            var key = GetMovieUserDataKey(this);
 
             if (string.IsNullOrWhiteSpace(key))
             {
-                key = this.GetProviderId(MetadataProviders.Imdb);
+                key = base.CreateUserDataKey();
             }
 
+            return key;
+        }
+
+        public static string GetMovieUserDataKey(BaseItem movie)
+        {
+            var key = movie.GetProviderId(MetadataProviders.Tmdb);
+
             if (string.IsNullOrWhiteSpace(key))
             {
-                key = base.CreateUserDataKey();
+                key = movie.GetProviderId(MetadataProviders.Imdb);
             }
 
             return key;

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

@@ -150,7 +150,7 @@ namespace MediaBrowser.Controller.Entities.TV
                 {
                     var series = Series;
 
-                    if (ParentIndexNumber.HasValue)
+                    if (series != null && ParentIndexNumber.HasValue)
                     {
                         var findNumber = ParentIndexNumber.Value;
 

+ 12 - 1
MediaBrowser.Controller/Entities/UserRootFolder.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Providers;
+using System.Runtime.Serialization;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Querying;
@@ -36,6 +37,16 @@ namespace MediaBrowser.Controller.Entities
             return PostFilterAndSort(result.Where(filter), query);
         }
 
+        [IgnoreDataMember]
+        protected override bool SupportsShortcutChildren
+        {
+            get
+            {
+                return true;
+            }
+        }
+
+        [IgnoreDataMember]
         public override bool IsPreSorted
         {
             get

+ 3 - 3
MediaBrowser.Controller/Entities/UserView.cs

@@ -1,10 +1,10 @@
-using System.Runtime.Serialization;
-using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Controller.TV;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using System;
 using System.Collections.Generic;
+using System.Runtime.Serialization;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Entities
@@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Entities
         public Guid ParentId { get; set; }
 
         public Guid? UserId { get; set; }
-
+        
         public static ITVSeriesManager TVSeriesManager;
         public static IPlaylistManager PlaylistManager;
 

+ 56 - 117
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -66,7 +66,7 @@ namespace MediaBrowser.Controller.Entities
                     {
                         var result = await _channelManager.GetChannelsInternal(new ChannelQuery
                         {
-                            UserId = user.Id.ToString("N"),
+                            UserId = user == null ? null : user.Id.ToString("N"),
                             Limit = query.Limit,
                             StartIndex = query.StartIndex
 
@@ -264,10 +264,7 @@ namespace MediaBrowser.Controller.Entities
 
         private async Task<QueryResult<BaseItem>> FindPlaylists(Folder parent, User user, InternalItemsQuery query)
         {
-            var collectionFolders = user.RootFolder.GetChildren(user, true).Select(i => i.Id).ToList();
-
-            var list = _playlistManager.GetPlaylists(user.Id.ToString("N"))
-                .Where(i => i.GetChildren(user, true).Any(media => _libraryManager.GetCollectionFolders(media).Select(c => c.Id).Any(collectionFolders.Contains)));
+            var list = _playlistManager.GetPlaylists(user.Id.ToString("N"));
 
             return GetResult(list, parent, query);
         }
@@ -288,14 +285,14 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            list.Add(await GetUserView(SpecialFolder.MusicLatest, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MusicPlaylists, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MusicAlbums, user, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MusicAlbumArtists, user, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicLatest, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicPlaylists, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicAlbums, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicAlbumArtists, "3", parent).ConfigureAwait(false));
             //list.Add(await GetUserView(SpecialFolder.MusicArtists, user, "4", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MusicSongs, user, "5", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MusicGenres, user, "6", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MusicFavorites, user, "7", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicSongs, "5", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicGenres, "6", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicFavorites, "7", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -304,9 +301,9 @@ namespace MediaBrowser.Controller.Entities
         {
             var list = new List<BaseItem>();
 
-            list.Add(await GetUserView(SpecialFolder.MusicFavoriteAlbums, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MusicFavoriteArtists, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MusicFavoriteSongs, user, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicFavoriteAlbums, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicFavoriteArtists, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicFavoriteSongs, "2", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -332,7 +329,7 @@ namespace MediaBrowser.Controller.Entities
 
                 })
                 .Where(i => i != null)
-                .Select(i => GetUserView(i.Name, SpecialFolder.MusicGenre, user, i.SortName, parent));
+                .Select(i => GetUserView(i.Name, SpecialFolder.MusicGenre, i.SortName, parent));
 
             var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
 
@@ -344,94 +341,42 @@ namespace MediaBrowser.Controller.Entities
             var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
                 .Where(i => !i.IsFolder)
                 .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase))
-                .OfType<IHasAlbumArtist>()
-                .SelectMany(i => i.AlbumArtists)
-                .DistinctNames()
-                .Select(i =>
-                {
-                    try
-                    {
-                        return _libraryManager.GetArtist(i);
-                    }
-                    catch
-                    {
-                        // Already logged at lower levels
-                        return null;
-                    }
-                })
-                .Where(i => i != null);
+                .OfType<IHasAlbumArtist>();
 
-            return GetResult(items, queryParent, query);
+            var artists = _libraryManager.GetAlbumArtists(items);
+
+            return GetResult(artists, queryParent, query);
         }
 
         private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var artists = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
+            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
                 .Where(i => !i.IsFolder)
-                .OfType<IHasAlbumArtist>()
-                .SelectMany(i => i.AlbumArtists)
-                .DistinctNames()
-                .Select(i =>
-                {
-                    try
-                    {
-                        return _libraryManager.GetArtist(i);
-                    }
-                    catch
-                    {
-                        // Already logged at lower levels
-                        return null;
-                    }
-                })
-                .Where(i => i != null);
+                .OfType<IHasAlbumArtist>();
+
+            var artists = _libraryManager.GetAlbumArtists(items);
 
             return GetResult(artists, parent, query);
         }
 
         private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var artists = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
+            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
                 .Where(i => !i.IsFolder)
-                .OfType<IHasArtist>()
-                .SelectMany(i => i.Artists)
-                .DistinctNames()
-                .Select(i =>
-                {
-                    try
-                    {
-                        return _libraryManager.GetArtist(i);
-                    }
-                    catch
-                    {
-                        // Already logged at lower levels
-                        return null;
-                    }
-                })
-                .Where(i => i != null);
+                .OfType<IHasArtist>();
+
+            var artists = _libraryManager.GetArtists(items);
 
             return GetResult(artists, parent, query);
         }
 
         private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var artists = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
+            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
                 .Where(i => !i.IsFolder)
-                .OfType<IHasAlbumArtist>()
-                .SelectMany(i => i.AlbumArtists)
-                .DistinctNames()
-                .Select(i =>
-                {
-                    try
-                    {
-                        return _libraryManager.GetArtist(i);
-                    }
-                    catch
-                    {
-                        // Already logged at lower levels
-                        return null;
-                    }
-                })
-                .Where(i => i != null && _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite);
+                .OfType<IHasAlbumArtist>();
+
+            var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite);
 
             return GetResult(artists, parent, query);
         }
@@ -498,12 +443,12 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            list.Add(await GetUserView(SpecialFolder.MovieResume, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MovieLatest, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MovieMovies, user, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MovieCollections, user, "3", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MovieFavorites, user, "4", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.MovieGenres, user, "5", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieResume, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieLatest, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieMovies, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieCollections, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieFavorites, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MovieGenres, "5", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -609,7 +554,7 @@ namespace MediaBrowser.Controller.Entities
 
                 })
                 .Where(i => i != null)
-                .Select(i => GetUserView(i.Name, SpecialFolder.MovieGenre, user, i.SortName, parent));
+                .Select(i => GetUserView(i.Name, SpecialFolder.MovieGenre, i.SortName, parent));
 
             var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
 
@@ -671,13 +616,13 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            list.Add(await GetUserView(SpecialFolder.TvResume, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.TvNextUp, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.TvLatest, user, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.TvShowSeries, user, "3", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.TvFavoriteSeries, user, "4", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.TvFavoriteEpisodes, user, "5", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.TvGenres, user, "6", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvResume, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvNextUp, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvLatest, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvShowSeries, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvFavoriteSeries, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvFavoriteEpisodes, "5", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.TvGenres, "6", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -692,11 +637,11 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            list.Add(await GetUserView(SpecialFolder.LatestGames, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameFavorites, user, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameSystems, user, "3", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameGenres, user, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.LatestGames, "0", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.RecentlyPlayedGames, "1", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.GameFavorites, "2", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.GameSystems, "3", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.GameGenres, "4", parent).ConfigureAwait(false));
 
             return GetResult(list, parent, query);
         }
@@ -794,7 +739,7 @@ namespace MediaBrowser.Controller.Entities
 
                 })
                 .Where(i => i != null)
-                .Select(i => GetUserView(i.Name, SpecialFolder.TvGenre, user, i.SortName, parent));
+                .Select(i => GetUserView(i.Name, SpecialFolder.TvGenre, i.SortName, parent));
 
             var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
 
@@ -846,7 +791,7 @@ namespace MediaBrowser.Controller.Entities
 
                 })
                 .Where(i => i != null)
-                .Select(i => GetUserView(i.Name, SpecialFolder.GameGenre, user, i.SortName, parent));
+                .Select(i => GetUserView(i.Name, SpecialFolder.GameGenre, i.SortName, parent));
 
             var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
 
@@ -1926,26 +1871,20 @@ namespace MediaBrowser.Controller.Entities
             var list = new List<BaseItem>();
 
             //list.Add(await GetUserSubView(SpecialFolder.LiveTvNowPlaying, user, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.LiveTvChannels, user, string.Empty, user.RootFolder).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, user, string.Empty, user.RootFolder).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.LiveTvChannels, string.Empty, user.RootFolder).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, string.Empty, user.RootFolder).ConfigureAwait(false));
 
             return GetResult(list, queryParent, query);
         }
 
-        private async Task<UserView> GetUserView(string name, string type, User user, string sortName, BaseItem parent)
+        private Task<UserView> GetUserView(string name, string type, string sortName, BaseItem parent)
         {
-            var view = await _userViewManager.GetUserSubView(name, parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
-                        .ConfigureAwait(false);
-
-            return view;
+            return _userViewManager.GetUserSubView(name, parent.Id.ToString("N"), type, sortName, CancellationToken.None);
         }
 
-        private async Task<UserView> GetUserView(string type, User user, string sortName, BaseItem parent)
+        private Task<UserView> GetUserView(string type, string sortName, BaseItem parent)
         {
-            var view = await _userViewManager.GetUserSubView(parent.Id.ToString("N"), type, user, sortName, CancellationToken.None)
-                        .ConfigureAwait(false);
-
-            return view;
+            return _userViewManager.GetUserSubView(parent.Id.ToString("N"), type, sortName, CancellationToken.None);
         }
 
         public static bool IsYearMismatched(BaseItem item, ILibraryManager libraryManager)

+ 51 - 3
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -5,12 +5,12 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Controller.Library
 {
@@ -61,7 +61,18 @@ namespace MediaBrowser.Controller.Library
         /// <param name="name">The name.</param>
         /// <returns>Task{Artist}.</returns>
         MusicArtist GetArtist(string name);
-
+        /// <summary>
+        /// Gets the album artists.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <returns>IEnumerable&lt;MusicArtist&gt;.</returns>
+        IEnumerable<MusicArtist> GetAlbumArtists(IEnumerable<IHasAlbumArtist> items);
+        /// <summary>
+        /// Gets the artists.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <returns>IEnumerable&lt;MusicArtist&gt;.</returns>
+        IEnumerable<MusicArtist> GetArtists(IEnumerable<IHasArtist> items);
         /// <summary>
         /// Gets a Studio
         /// </summary>
@@ -340,7 +351,37 @@ namespace MediaBrowser.Controller.Library
         Task<UserView> GetNamedView(User user,
             string name, 
             string viewType, 
-            string sortName, 
+            string sortName,
+            CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the named view.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <param name="viewType">Type of the view.</param>
+        /// <param name="sortName">Name of the sort.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;UserView&gt;.</returns>
+        Task<UserView> GetNamedView(string name,
+            string viewType,
+            string sortName,
+            CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the named view.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <param name="parentId">The parent identifier.</param>
+        /// <param name="viewType">Type of the view.</param>
+        /// <param name="sortName">Name of the sort.</param>
+        /// <param name="uniqueId">The unique identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;UserView&gt;.</returns>
+        Task<UserView> GetNamedView(string name,
+            string parentId,
+            string viewType,
+            string sortName,
+            string uniqueId,
             CancellationToken cancellationToken);
 
         /// <summary>
@@ -461,5 +502,12 @@ namespace MediaBrowser.Controller.Library
         /// <param name="query">The query.</param>
         /// <returns>List&lt;System.String&gt;.</returns>
         List<string> GetPeopleNames(InternalPeopleQuery query);
+
+        /// <summary>
+        /// Queries the items.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>QueryResult&lt;BaseItem&gt;.</returns>
+        QueryResult<BaseItem> QueryItems(InternalItemsQuery query);
     }
 }

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

@@ -11,4 +11,9 @@ namespace MediaBrowser.Controller.Library
         /// <returns>System.String.</returns>
         string GetSavePath(IHasMetadata item);
     }
+
+    public interface IConfigurableProvider
+    {
+        bool IsEnabled { get; }
+    }
 }

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

@@ -16,10 +16,10 @@ namespace MediaBrowser.Controller.Library
         /// <summary>
         /// Gets the instant mix from artist.
         /// </summary>
-        /// <param name="name">The name.</param>
+        /// <param name="artist">The artist.</param>
         /// <param name="user">The user.</param>
         /// <returns>IEnumerable{Audio}.</returns>
-        IEnumerable<Audio> GetInstantMixFromArtist(string name, User user);
+        IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist artist, User user);
         /// <summary>
         /// Gets the instant mix from genre.
         /// </summary>

+ 2 - 3
MediaBrowser.Controller/Library/IUserViewManager.cs

@@ -12,10 +12,9 @@ namespace MediaBrowser.Controller.Library
     {
         Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken);
 
-        Task<UserView> GetUserSubView(string name, string parentId, string type, User user, string sortName,
-            CancellationToken cancellationToken);
+        Task<UserView> GetUserSubView(string name, string parentId, string type, string sortName, CancellationToken cancellationToken);
 
-        Task<UserView> GetUserSubView(string category, string type, User user, string sortName, CancellationToken cancellationToken);
+        Task<UserView> GetUserSubView(string category, string type, string sortName, CancellationToken cancellationToken);
 
         List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request);
     }

+ 5 - 1
MediaBrowser.Controller/LiveTv/ChannelInfo.cs

@@ -48,6 +48,10 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
         public bool? HasImage { get; set; }
-
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is favorite.
+        /// </summary>
+        /// <value><c>null</c> if [is favorite] contains no value, <c>true</c> if [is favorite]; otherwise, <c>false</c>.</value>
+        public bool? IsFavorite { get; set; }
     }
 }

+ 15 - 0
MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs

@@ -0,0 +1,15 @@
+using MediaBrowser.Model.Entities;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+    public interface IHasRegistrationInfo
+    {
+        /// <summary>
+        /// Gets the registration information.
+        /// </summary>
+        /// <param name="feature">The feature.</param>
+        /// <returns>Task&lt;MBRegistrationRecord&gt;.</returns>
+        Task<MBRegistrationRecord> GetRegistrationInfo(string feature);
+    }
+}

+ 19 - 0
MediaBrowser.Controller/LiveTv/IListingsProvider.cs

@@ -0,0 +1,19 @@
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+    public interface IListingsProvider
+    {
+        string Name { get; }
+        string Type { get; }
+        Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken);
+        Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken);
+        Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings);
+        Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location);
+    }
+}

+ 55 - 11
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
@@ -56,20 +57,23 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="id">The identifier.</param>
         /// <returns>Task.</returns>
         Task CancelSeriesTimer(string id);
-        
+
         /// <summary>
         /// Adds the parts.
         /// </summary>
         /// <param name="services">The services.</param>
-        void AddParts(IEnumerable<ILiveTvService> services);
+        /// <param name="tunerHosts">The tuner hosts.</param>
+        /// <param name="listingProviders">The listing providers.</param>
+        void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders);
 
         /// <summary>
         /// Gets the channels.
         /// </summary>
         /// <param name="query">The query.</param>
+        /// <param name="options">The options.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>IEnumerable{Channel}.</returns>
-        Task<QueryResult<ChannelInfoDto>> GetChannels(LiveTvChannelQuery query, CancellationToken cancellationToken);
+        Task<QueryResult<ChannelInfoDto>> GetChannels(LiveTvChannelQuery query, DtoOptions options, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the recording.
@@ -171,14 +175,15 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="user">The user.</param>
         /// <returns>Task{ProgramInfoDto}.</returns>
         Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null);
-        
+
         /// <summary>
         /// Gets the programs.
         /// </summary>
         /// <param name="query">The query.</param>
+        /// <param name="options">The options.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>IEnumerable{ProgramInfo}.</returns>
-        Task<QueryResult<BaseItemDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken);
+        Task<QueryResult<BaseItemDto>> GetPrograms(ProgramQuery query, DtoOptions options, CancellationToken cancellationToken);
 
         /// <summary>
         /// Updates the timer.
@@ -238,10 +243,10 @@ namespace MediaBrowser.Controller.LiveTv
         /// Gets the recommended programs.
         /// </summary>
         /// <param name="query">The query.</param>
+        /// <param name="options">The options.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{QueryResult{ProgramInfoDto}}.</returns>
-        Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query,
-            CancellationToken cancellationToken);
+        Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the recommended programs internal.
@@ -249,8 +254,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="query">The query.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;QueryResult&lt;LiveTvProgram&gt;&gt;.</returns>
-        Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query,
-            CancellationToken cancellationToken);
+        Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the live tv information.
@@ -270,10 +274,9 @@ namespace MediaBrowser.Controller.LiveTv
         /// <summary>
         /// Gets the live tv folder.
         /// </summary>
-        /// <param name="userId">The user identifier.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>BaseItemDto.</returns>
-        Task<Folder> GetInternalLiveTvFolder(string userId, CancellationToken cancellationToken);
+        Task<Folder> GetInternalLiveTvFolder(CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the live tv folder.
@@ -337,5 +340,46 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="dto">The dto.</param>
         /// <param name="user">The user.</param>
         void AddInfoToProgramDto(BaseItem item, BaseItemDto dto, User user = null);
+        /// <summary>
+        /// Saves the tuner host.
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <returns>Task.</returns>
+        Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info);
+        /// <summary>
+        /// Saves the listing provider.
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <param name="validateLogin">if set to <c>true</c> [validate login].</param>
+        /// <param name="validateListings">if set to <c>true</c> [validate listings].</param>
+        /// <returns>Task.</returns>
+        Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings);
+        /// <summary>
+        /// Gets the lineups.
+        /// </summary>
+        /// <param name="providerType">Type of the provider.</param>
+        /// <param name="providerId">The provider identifier.</param>
+        /// <param name="country">The country.</param>
+        /// <param name="location">The location.</param>
+        /// <returns>Task&lt;List&lt;NameIdPair&gt;&gt;.</returns>
+        Task<List<NameIdPair>> GetLineups(string providerType, string providerId, string country, string location);
+
+        /// <summary>
+        /// Gets the registration information.
+        /// </summary>
+        /// <param name="channelId">The channel identifier.</param>
+        /// <param name="programId">The program identifier.</param>
+        /// <param name="feature">The feature.</param>
+        /// <returns>Task&lt;MBRegistrationRecord&gt;.</returns>
+        Task<MBRegistrationRecord> GetRegistrationInfo(string channelId, string programId, string feature);
+
+        /// <summary>
+        /// Adds the channel information.
+        /// </summary>
+        /// <param name="dto">The dto.</param>
+        /// <param name="channel">The channel.</param>
+        /// <param name="options">The options.</param>
+        /// <param name="user">The user.</param>
+        void AddChannelInfo(BaseItemDto dto, LiveTvChannel channel, DtoOptions options, User user);
     }
 }

+ 3 - 1
MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs

@@ -37,10 +37,12 @@ namespace MediaBrowser.Controller.LiveTv
 
         string ExternalId { get; set; }
         string EpisodeTitle { get; set; }
-        bool IsSeries { get; set; }
         string SeriesTimerId { get; set; }
         RecordingStatus Status { get; set; }
         DateTime? EndDate { get; set; }
         ChannelType ChannelType { get; set; }
+        DateTime DateLastSaved { get; set; }
+        DateTime DateCreated { get; set; }
+        DateTime DateModified { get; set; }
     }
 }

+ 55 - 0
MediaBrowser.Controller/LiveTv/ITunerHost.cs

@@ -0,0 +1,55 @@
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+    public interface ITunerHost
+    {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+        /// <summary>
+        /// Gets the type.
+        /// </summary>
+        /// <value>The type.</value>
+        string Type { get; }
+        /// <summary>
+        /// Gets the channels.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;IEnumerable&lt;ChannelInfo&gt;&gt;.</returns>
+        Task<IEnumerable<ChannelInfo>> GetChannels(CancellationToken cancellationToken);
+        /// <summary>
+        /// Gets the tuner infos.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;List&lt;LiveTvTunerInfo&gt;&gt;.</returns>
+        Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken);
+        /// <summary>
+        /// Gets the channel stream.
+        /// </summary>
+        /// <param name="channelId">The channel identifier.</param>
+        /// <param name="streamId">The stream identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
+        Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken);
+        /// <summary>
+        /// Gets the channel stream media sources.
+        /// </summary>
+        /// <param name="channelId">The channel identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
+        Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
+        /// <summary>
+        /// Validates the specified information.
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <returns>Task.</returns>
+        Task Validate(TunerHostInfo info);
+    }
+}

+ 30 - 0
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.LiveTv;
@@ -17,9 +18,24 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>System.String.</returns>
         protected override string CreateUserDataKey()
         {
+            if (IsMovie)
+            {
+                var key = Movie.GetMovieUserDataKey(this);
+
+                if (!string.IsNullOrWhiteSpace(key))
+                {
+                    return key;
+                }
+            }
             return GetClientTypeName() + "-" + Name;
         }
 
+        /// <summary>
+        /// Gets or sets the etag.
+        /// </summary>
+        /// <value>The etag.</value>
+        public string Etag { get; set; }
+        
         /// <summary>
         /// Id of the program.
         /// </summary>
@@ -227,5 +243,19 @@ namespace MediaBrowser.Controller.LiveTv
             info.IsMovie = IsMovie; 
             return info;
         }
+
+        public override bool SupportsPeople
+        {
+            get
+            {
+                // Optimization
+                if (IsNews || IsSports)
+                {
+                    return false;
+                }
+
+                return base.SupportsPeople;
+            }
+        }
     }
 }

+ 7 - 1
MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs

@@ -34,10 +34,16 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The tuners.</value>
         public List<LiveTvTunerInfo> Tuners { get; set; }
-
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is visible.
+        /// </summary>
+        /// <value><c>true</c> if this instance is visible; otherwise, <c>false</c>.</value>
+        public bool IsVisible { get; set; }
+        
         public LiveTvServiceStatusInfo()
         {
             Tuners = new List<LiveTvTunerInfo>();
+            IsVisible = true;
         }
     }
 }

+ 11 - 0
MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -40,6 +41,16 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>System.String.</returns>
         protected override string CreateUserDataKey()
         {
+            if (IsMovie)
+            {
+                var key = Movie.GetMovieUserDataKey(this);
+
+                if (!string.IsNullOrWhiteSpace(key))
+                {
+                    return key;
+                }
+            }
+            
             var name = GetClientTypeName();
 
             if (!string.IsNullOrEmpty(ProgramId))

+ 35 - 0
MediaBrowser.Controller/LiveTv/ProgramInfo.cs

@@ -33,6 +33,11 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The overview.</value>
         public string Overview { get; set; }
+        /// <summary>
+        /// Gets or sets the short overview.
+        /// </summary>
+        /// <value>The short overview.</value>
+        public string ShortOverview { get; set; }
 
         /// <summary>
         /// The start date of the program, in UTC.
@@ -150,6 +155,36 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The production year.</value>
         public int? ProductionYear { get; set; }
+        /// <summary>
+        /// Gets or sets the home page URL.
+        /// </summary>
+        /// <value>The home page URL.</value>
+        public string HomePageUrl { get; set; }
+        /// <summary>
+        /// Gets or sets the series identifier.
+        /// </summary>
+        /// <value>The series identifier.</value>
+        public string SeriesId { get; set; }
+        /// <summary>
+        /// Gets or sets the show identifier.
+        /// </summary>
+        /// <value>The show identifier.</value>
+        public string ShowId { get; set; }
+        /// <summary>
+        /// Gets or sets the season number.
+        /// </summary>
+        /// <value>The season number.</value>
+        public int? SeasonNumber { get; set; }
+        /// <summary>
+        /// Gets or sets the episode number.
+        /// </summary>
+        /// <value>The episode number.</value>
+        public int? EpisodeNumber { get; set; }
+        /// <summary>
+        /// Gets or sets the etag.
+        /// </summary>
+        /// <value>The etag.</value>
+        public string Etag { get; set; }
         
         public ProgramInfo()
         {

Некоторые файлы не были показаны из-за большого количества измененных файлов