2
0
Luke Pulverenti 7 жил өмнө
parent
commit
4e52c027bc

+ 23 - 0
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -2622,6 +2622,11 @@ namespace Emby.Server.Implementations.Data
                 groups.Add("PresentationUniqueKey");
             }
 
+            if (query.GroupBySeriesPresentationUniqueKey)
+            {
+                groups.Add("SeriesPresentationUniqueKey");
+            }
+
             if (groups.Count > 0)
             {
                 return " Group by " + string.Join(",", groups.ToArray());
@@ -2934,6 +2939,10 @@ namespace Emby.Server.Implementations.Data
                 {
                     commandText += " select count (distinct PresentationUniqueKey)" + GetFromText();
                 }
+                else if (query.GroupBySeriesPresentationUniqueKey)
+                {
+                    commandText += " select count (distinct SeriesPresentationUniqueKey)" + GetFromText();
+                }
                 else
                 {
                     commandText += " select count (guid)" + GetFromText();
@@ -3079,6 +3088,11 @@ namespace Emby.Server.Implementations.Data
             }
             if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
             {
+                if (query.GroupBySeriesPresentationUniqueKey)
+                {
+                    return new Tuple<string, bool>("MAX(LastPlayedDate)", false);
+                }
+
                 return new Tuple<string, bool>("LastPlayedDate", false);
             }
             if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
@@ -3353,6 +3367,10 @@ namespace Emby.Server.Implementations.Data
                 {
                     commandText += " select count (distinct PresentationUniqueKey)" + GetFromText();
                 }
+                else if (query.GroupBySeriesPresentationUniqueKey)
+                {
+                    commandText += " select count (distinct SeriesPresentationUniqueKey)" + GetFromText();
+                }
                 else
                 {
                     commandText += " select count (guid)" + GetFromText();
@@ -4640,6 +4658,11 @@ namespace Emby.Server.Implementations.Data
                 return false;
             }
 
+            if (query.GroupBySeriesPresentationUniqueKey)
+            {
+                return false;
+            }
+
             if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
             {
                 return false;

+ 48 - 140
Emby.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -6,19 +6,16 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
-using System.IO.Compression;
 using System.Net;
 using System.Runtime.Serialization;
 using System.Text;
 using System.Threading.Tasks;
 using System.Xml;
-using Emby.Server.Implementations.HttpServer;
 using Emby.Server.Implementations.Services;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Services;
 using IRequest = MediaBrowser.Model.Services.IRequest;
 using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
-using StreamWriter = Emby.Server.Implementations.HttpServer.StreamWriter;
 
 namespace Emby.Server.Implementations.HttpServer
 {
@@ -193,50 +190,37 @@ namespace Emby.Server.Implementations.HttpServer
         /// <returns></returns>
         public object ToOptimizedResult<T>(IRequest request, T dto)
         {
-            var compressionType = GetCompressionType(request);
-            if (compressionType == null)
-            {
-                var contentType = request.ResponseContentType;
-
-                switch (GetRealContentType(contentType))
-                {
-                    case "application/xml":
-                    case "text/xml":
-                    case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
-                        return SerializeToXmlString(dto);
-
-                    case "application/json":
-                    case "text/json":
-                        return _jsonSerializer.SerializeToString(dto);
-                }
-            }
+            var contentType = request.ResponseContentType;
 
-            // Do not use the memoryStreamFactory here, they don't place nice with compression
-            using (var ms = new MemoryStream())
+            switch (GetRealContentType(contentType))
             {
-                var contentType = request.ResponseContentType;
-                var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
+                case "application/xml":
+                case "text/xml":
+                case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
+                    return SerializeToXmlString(dto);
 
-                writerFn(dto, ms);
+                case "application/json":
+                case "text/json":
+                    return _jsonSerializer.SerializeToString(dto);
+                default:
+                    {
+                        var ms = new MemoryStream();
+                        var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
 
-                ms.Position = 0;
+                        writerFn(dto, ms);
+                        
+                        ms.Position = 0;
 
-                var responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+                        if (string.Equals(request.Verb, "head", StringComparison.OrdinalIgnoreCase))
+                        {
+                            return GetHttpResult(new byte[] { }, contentType, true);
+                        }
 
-                return GetCompressedResult(ms, compressionType, responseHeaders, false, request.ResponseContentType).Result;
+                        return GetHttpResult(ms, contentType, true);
+                    }
             }
         }
 
-        private static Stream GetCompressionStream(Stream outputStream, string compressionType)
-        {
-            if (compressionType == "deflate")
-                return new DeflateStream(outputStream, CompressionMode.Compress, true);
-            if (compressionType == "gzip")
-                return new GZipStream(outputStream, CompressionMode.Compress, true);
-
-            throw new NotSupportedException(compressionType);
-        }
-
         public static string GetRealContentType(string contentType)
         {
             return contentType == null
@@ -568,123 +552,47 @@ namespace Emby.Server.Implementations.HttpServer
             var contentType = options.ContentType;
             var responseHeaders = options.ResponseHeaders;
 
-            var requestedCompressionType = GetCompressionType(requestContext);
+            //var requestedCompressionType = GetCompressionType(requestContext);
 
-            if (!compress || string.IsNullOrEmpty(requestedCompressionType))
-            {
-                var rangeHeader = requestContext.Headers.Get("Range");
-
-                if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path))
-                {
-                    return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
-                    {
-                        OnComplete = options.OnComplete,
-                        OnError = options.OnError,
-                        FileShare = options.FileShare
-                    };
-                }
-
-                if (!string.IsNullOrWhiteSpace(rangeHeader))
-                {
-                    var stream = await factoryFn().ConfigureAwait(false);
-
-                    return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger)
-                    {
-                        OnComplete = options.OnComplete
-                    };
-                }
-                else
-                {
-                    var stream = await factoryFn().ConfigureAwait(false);
-
-                    responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
-
-                    if (isHeadRequest)
-                    {
-                        stream.Dispose();
-
-                        return GetHttpResult(new byte[] { }, contentType, true);
-                    }
-
-                    return new StreamWriter(stream, contentType, _logger)
-                    {
-                        OnComplete = options.OnComplete,
-                        OnError = options.OnError
-                    };
-                }
-            }
+            var rangeHeader = requestContext.Headers.Get("Range");
 
-            using (var stream = await factoryFn().ConfigureAwait(false))
+            if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path))
             {
-                return await GetCompressedResult(stream, requestedCompressionType, responseHeaders, isHeadRequest, contentType).ConfigureAwait(false);
+                return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
+                {
+                    OnComplete = options.OnComplete,
+                    OnError = options.OnError,
+                    FileShare = options.FileShare
+                };
             }
-        }
 
-        private async Task<IHasHeaders> GetCompressedResult(Stream stream,
-            string requestedCompressionType,
-            IDictionary<string, string> responseHeaders,
-            bool isHeadRequest,
-            string contentType)
-        {
-            using (var reader = new MemoryStream())
+            if (!string.IsNullOrWhiteSpace(rangeHeader))
             {
-                await stream.CopyToAsync(reader).ConfigureAwait(false);
-
-                reader.Position = 0;
-                var content = reader.ToArray();
+                var stream = await factoryFn().ConfigureAwait(false);
 
-                if (content.Length >= 1024)
+                return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger)
                 {
-                    content = Compress(content, requestedCompressionType);
-                    responseHeaders["Content-Encoding"] = requestedCompressionType;
-                }
+                    OnComplete = options.OnComplete
+                };
+            }
+            else
+            {
+                var stream = await factoryFn().ConfigureAwait(false);
 
-                responseHeaders["Vary"] = "Accept-Encoding";
-                responseHeaders["Content-Length"] = content.Length.ToString(UsCulture);
+                responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
 
                 if (isHeadRequest)
                 {
+                    stream.Dispose();
+
                     return GetHttpResult(new byte[] { }, contentType, true);
                 }
 
-                return GetHttpResult(content, contentType, true, responseHeaders);
-            }
-        }
-
-        private byte[] Compress(byte[] bytes, string compressionType)
-        {
-            if (compressionType == "deflate")
-                return Deflate(bytes);
-
-            if (compressionType == "gzip")
-                return GZip(bytes);
-
-            throw new NotSupportedException(compressionType);
-        }
-
-        private byte[] Deflate(byte[] bytes)
-        {
-            // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream
-            // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream
-            using (var ms = new MemoryStream())
-            using (var zipStream = new DeflateStream(ms, CompressionMode.Compress))
-            {
-                zipStream.Write(bytes, 0, bytes.Length);
-                zipStream.Dispose();
-
-                return ms.ToArray();
-            }
-        }
-
-        private byte[] GZip(byte[] buffer)
-        {
-            using (var ms = new MemoryStream())
-            using (var zipStream = new GZipStream(ms, CompressionMode.Compress))
-            {
-                zipStream.Write(buffer, 0, buffer.Length);
-                zipStream.Dispose();
-
-                return ms.ToArray();
+                return new StreamWriter(stream, contentType, _logger)
+                {
+                    OnComplete = options.OnComplete,
+                    OnError = options.OnError
+                };
             }
         }
 

+ 37 - 2
Emby.Server.Implementations/Library/UserViewManager.cs

@@ -269,7 +269,41 @@ namespace Emby.Server.Implementations.Library
                 return new List<BaseItem>();
             }
 
-            var excludeItemTypes = includeItemTypes.Length == 0 ? new[]
+            var mediaTypes = new List<string>();
+
+            if (includeItemTypes.Length == 0)
+            {
+                foreach (var parent in parents.OfType<ICollectionFolder>())
+                {
+                    switch (parent.CollectionType)
+                    {
+                        case CollectionType.Books:
+                            mediaTypes.Add(MediaType.Book);
+                            break;
+                        case CollectionType.Games:
+                            mediaTypes.Add(MediaType.Game);
+                            break;
+                        case CollectionType.Music:
+                            mediaTypes.Add(MediaType.Audio);
+                            break;
+                        case CollectionType.Photos:
+                            mediaTypes.Add(MediaType.Photo);
+                            mediaTypes.Add(MediaType.Video);
+                            break;
+                        case CollectionType.HomeVideos:
+                            mediaTypes.Add(MediaType.Photo);
+                            mediaTypes.Add(MediaType.Video);
+                            break;
+                        default:
+                            mediaTypes.Add(MediaType.Video);
+                            break;
+                    }
+                }
+
+                mediaTypes = mediaTypes.Distinct().ToList();
+            }
+
+            var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[]
             {
                 typeof(Person).Name,
                 typeof(Studio).Name,
@@ -290,7 +324,8 @@ namespace Emby.Server.Implementations.Library
                 IsVirtualItem = false,
                 Limit = limit * 5,
                 IsPlayed = isPlayed,
-                DtoOptions = options
+                DtoOptions = options,
+                MediaTypes = mediaTypes.ToArray()
             };
 
             if (parents.Count == 0)

+ 1 - 2
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -1632,7 +1632,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     return;
                 }
 
-                var episodesToDelete = (librarySeries.GetItems(new InternalItemsQuery
+                var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery
                 {
                     SortBy = new[] { ItemSortBy.DateCreated },
                     SortOrder = SortOrder.Descending,
@@ -1642,7 +1642,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     DtoOptions = new DtoOptions(true)
 
                 }))
-                    .Items
                     .Where(i => i.LocationType == LocationType.FileSystem && _fileSystem.FileExists(i.Path))
                     .Skip(seriesTimer.KeepUpTo - 1)
                     .ToList();

+ 6 - 3
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -1845,6 +1845,9 @@ namespace Emby.Server.Implementations.LiveTv
         public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, List<ItemFields> fields, User user = null)
         {
             var programTuples = new List<Tuple<BaseItemDto, string, string, string>>();
+            var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
+            var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
+            var hasServiceName = fields.Contains(ItemFields.ServiceName);
 
             foreach (var tuple in tuples)
             {
@@ -1887,7 +1890,7 @@ namespace Emby.Server.Implementations.LiveTv
                     dto.IsPremiere = program.IsPremiere;
                 }
 
-                if (fields.Contains(ItemFields.ChannelInfo))
+                if (hasChannelInfo || hasChannelImage)
                 {
                     var channel = GetInternalChannel(program.ChannelId);
 
@@ -1897,7 +1900,7 @@ namespace Emby.Server.Implementations.LiveTv
                         dto.MediaType = channel.MediaType;
                         dto.ChannelNumber = channel.Number;
 
-                        if (channel.HasImage(ImageType.Primary))
+                        if (hasChannelImage && channel.HasImage(ImageType.Primary))
                         {
                             dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
                         }
@@ -1906,7 +1909,7 @@ namespace Emby.Server.Implementations.LiveTv
 
                 var serviceName = program.ServiceName;
 
-                if (fields.Contains(ItemFields.ServiceName))
+                if (hasServiceName)
                 {
                     dto.ServiceName = serviceName;
                 }

+ 2 - 2
Emby.Server.Implementations/Session/SessionManager.cs

@@ -1089,7 +1089,7 @@ namespace Emby.Server.Implementations.Session
             {
                 var folder = (Folder)item;
 
-                var itemsResult = folder.GetItems(new InternalItemsQuery(user)
+                var itemsResult = folder.GetItemList(new InternalItemsQuery(user)
                 {
                     Recursive = true,
                     IsFolder = false,
@@ -1104,7 +1104,7 @@ namespace Emby.Server.Implementations.Session
 
                 });
 
-                return FilterToSingleMediaType(itemsResult.Items)
+                return FilterToSingleMediaType(itemsResult)
                     .OrderBy(i => i.SortName)
                     .ToList();
             }

+ 34 - 17
Emby.Server.Implementations/TV/TVSeriesManager.cs

@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.TV
             int? limit = null;
             if (!string.IsNullOrWhiteSpace(request.SeriesId))
             {
-                var series = _libraryManager.GetItemById(request.SeriesId);
+                var series = _libraryManager.GetItemById(request.SeriesId) as Series;
 
                 if (series != null)
                 {
@@ -51,17 +51,22 @@ namespace Emby.Server.Implementations.TV
                 }
             }
 
-            if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
+            if (!string.IsNullOrWhiteSpace(presentationUniqueKey))
+            {
+                return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
+            }
+
+            if (limit.HasValue)
             {
                 limit = limit.Value + 10;
             }
 
             var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
-                IncludeItemTypes = new[] { typeof(Series).Name },
-                SortBy = new[] { ItemSortBy.SeriesDatePlayed },
+                IncludeItemTypes = new[] { typeof(Episode).Name },
+                SortBy = new[] { ItemSortBy.DatePlayed },
                 SortOrder = SortOrder.Descending,
-                PresentationUniqueKey = presentationUniqueKey,
+                SeriesPresentationUniqueKey = presentationUniqueKey,
                 Limit = limit,
                 ParentId = parentIdGuid,
                 Recursive = true,
@@ -69,11 +74,12 @@ namespace Emby.Server.Implementations.TV
                 {
                     Fields = new List<ItemFields>
                     {
-                        ItemFields.PresentationUniqueKey
+                        ItemFields.SeriesPresentationUniqueKey
                     }
-                }
+                },
+                GroupBySeriesPresentationUniqueKey = true
 
-            }).Cast<Series>().Select(GetUniqueSeriesKey);
+            }).Cast<Episode>().Select(GetUniqueSeriesKey);
 
             // Avoid implicitly captured closure
             var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
@@ -94,7 +100,7 @@ namespace Emby.Server.Implementations.TV
             int? limit = null;
             if (!string.IsNullOrWhiteSpace(request.SeriesId))
             {
-                var series = _libraryManager.GetItemById(request.SeriesId);
+                var series = _libraryManager.GetItemById(request.SeriesId) as Series;
 
                 if (series != null)
                 {
@@ -103,28 +109,34 @@ namespace Emby.Server.Implementations.TV
                 }
             }
 
-            if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
+            if (!string.IsNullOrWhiteSpace(presentationUniqueKey))
+            {
+                return GetResult(GetNextUpEpisodes(request, user, new [] { presentationUniqueKey }, dtoOptions), request);
+            }
+
+            if (limit.HasValue)
             {
                 limit = limit.Value + 10;
             }
 
             var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
-                IncludeItemTypes = new[] { typeof(Series).Name },
-                SortBy = new[] { ItemSortBy.SeriesDatePlayed },
+                IncludeItemTypes = new[] { typeof(Episode).Name },
+                SortBy = new[] { ItemSortBy.DatePlayed },
                 SortOrder = SortOrder.Descending,
-                PresentationUniqueKey = presentationUniqueKey,
+                SeriesPresentationUniqueKey = presentationUniqueKey,
                 Limit = limit,
                 DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions
                 {
                     Fields = new List<ItemFields>
                     {
-                        ItemFields.PresentationUniqueKey
+                        ItemFields.SeriesPresentationUniqueKey
                     },
                     EnableImages = false
-                }
+                },
+                GroupBySeriesPresentationUniqueKey = true
 
-            }, parentsFolders.Cast<BaseItem>().ToList()).Cast<Series>().Select(GetUniqueSeriesKey);
+            }, parentsFolders.Cast<BaseItem>().ToList()).Cast<Episode>().Select(GetUniqueSeriesKey);
 
             // Avoid implicitly captured closure
             var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
@@ -167,7 +179,12 @@ namespace Emby.Server.Implementations.TV
                 .Where(i => i != null);
         }
 
-        private string GetUniqueSeriesKey(BaseItem series)
+        private string GetUniqueSeriesKey(Episode episode)
+        {
+            return episode.SeriesPresentationUniqueKey;
+        }
+
+        private string GetUniqueSeriesKey(Series series)
         {
             return series.GetPresentationUniqueKey();
         }

+ 2 - 2
Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs

@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.UserViews
 
             var recursive = !new[] { CollectionType.Playlists, CollectionType.Channels }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
 
-            var result = view.GetItems(new InternalItemsQuery
+            var result = view.GetItemList(new InternalItemsQuery
             {
                 CollapseBoxSetItems = false,
                 Recursive = recursive,
@@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.UserViews
 
             });
 
-            var items = result.Items.Select(i =>
+            var items = result.Select(i =>
             {
                 var episode = i as Episode;
                 if (episode != null)

+ 4 - 4
Emby.Server.Implementations/UserViews/DynamicImageProvider.cs

@@ -70,19 +70,19 @@ namespace Emby.Server.Implementations.UserViews
             if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) ||
                 string.Equals(view.ViewType, SpecialFolder.TvGenre, StringComparison.OrdinalIgnoreCase))
             {
-                var userItemsResult = view.GetItems(new InternalItemsQuery
+                var userItemsResult = view.GetItemList(new InternalItemsQuery
                 {
                     CollapseBoxSetItems = false,
                     DtoOptions = new DtoOptions(false)
                 });
 
-                return userItemsResult.Items.ToList();
+                return userItemsResult.ToList();
             }
 
             var isUsingCollectionStrip = IsUsingCollectionStrip(view);
             var recursive = isUsingCollectionStrip && !new[] { CollectionType.Channels, CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
 
-            var result = view.GetItems(new InternalItemsQuery
+            var result = view.GetItemList(new InternalItemsQuery
             {
                 User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
                 CollapseBoxSetItems = false,
@@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.UserViews
                 DtoOptions = new DtoOptions(false)
             });
 
-            var items = result.Items.Select(i =>
+            var items = result.Select(i =>
             {
                 var episode = i as Episode;
                 if (episode != null)

+ 2 - 2
MediaBrowser.Api/FilterService.cs

@@ -61,9 +61,9 @@ namespace MediaBrowser.Api
                user == null ? _libraryManager.RootFolder : user.RootFolder :
                parentItem;
 
-            var result = ((Folder)item).GetItems(GetItemsQuery(request, user));
+            var result = ((Folder)item).GetItemList(GetItemsQuery(request, user));
 
-            return ToOptimizedResult(GetFilters(result.Items));
+            return ToOptimizedResult(GetFilters(result.ToArray()));
         }
 
         private QueryFilters GetFilters(BaseItem[] items)

+ 2 - 2
MediaBrowser.Api/TvShowsService.cs

@@ -438,14 +438,14 @@ namespace MediaBrowser.Api
                 throw new ResourceNotFoundException("Series not found");
             }
 
-            var seasons = (series.GetItems(new InternalItemsQuery(user)
+            var seasons = (series.GetItemList(new InternalItemsQuery(user)
             {
                 IsMissing = request.IsMissing,
                 IsVirtualUnaired = request.IsVirtualUnaired,
                 IsSpecialSeason = request.IsSpecialSeason,
                 AdjacentTo = request.AdjacentTo
 
-            })).Items.OfType<Season>();
+            })).OfType<Season>();
 
             var dtoOptions = GetDtoOptions(_authContext, request);
 

+ 27 - 6
MediaBrowser.Controller/Entities/Folder.cs

@@ -970,6 +970,27 @@ namespace MediaBrowser.Controller.Entities
             return GetItemsInternal(query);
         }
 
+        public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
+        {
+            query.EnableTotalRecordCount = false;
+
+            if (query.ItemIds.Length > 0)
+            {
+                var result = LibraryManager.GetItemList(query);
+
+                if (query.SortBy.Length == 0)
+                {
+                    var ids = query.ItemIds.ToList();
+
+                    // Try to preserve order
+                    result = result.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
+                }
+                return result;
+            }
+
+            return GetItemsInternal(query).Items;
+        }
+
         protected virtual QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
         {
             if (SourceType == SourceType.Channel)
@@ -1375,10 +1396,10 @@ namespace MediaBrowser.Controller.Entities
                 query.IsVirtualItem = false;
             }
 
-            var itemsResult = GetItems(query);
+            var itemsResult = GetItemList(query);
 
             // Sweep through recursively and update status
-            var tasks = itemsResult.Items.Select(c => c.MarkPlayed(user, datePlayed, resetPosition));
+            var tasks = itemsResult.Select(c => c.MarkPlayed(user, datePlayed, resetPosition));
 
             await Task.WhenAll(tasks).ConfigureAwait(false);
         }
@@ -1390,7 +1411,7 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>Task.</returns>
         public override async Task MarkUnplayed(User user)
         {
-            var itemsResult = GetItems(new InternalItemsQuery
+            var itemsResult = GetItemList(new InternalItemsQuery
             {
                 User = user,
                 Recursive = true,
@@ -1400,14 +1421,14 @@ namespace MediaBrowser.Controller.Entities
             });
 
             // Sweep through recursively and update status
-            var tasks = itemsResult.Items.Select(c => c.MarkUnplayed(user));
+            var tasks = itemsResult.Select(c => c.MarkUnplayed(user));
 
             await Task.WhenAll(tasks).ConfigureAwait(false);
         }
 
         public override bool IsPlayed(User user)
         {
-            var itemsResult = GetItems(new InternalItemsQuery(user)
+            var itemsResult = GetItemList(new InternalItemsQuery(user)
             {
                 Recursive = true,
                 IsFolder = false,
@@ -1416,7 +1437,7 @@ namespace MediaBrowser.Controller.Entities
 
             });
 
-            return itemsResult.Items
+            return itemsResult
                 .All(i => i.IsPlayed(user));
         }
 

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

@@ -161,6 +161,7 @@ namespace MediaBrowser.Controller.Entities
         public string SeriesPresentationUniqueKey { get; set; }
 
         public bool GroupByPresentationUniqueKey { get; set; }
+        public bool GroupBySeriesPresentationUniqueKey { get; set; }
         public bool EnableTotalRecordCount { get; set; }
         public bool ForceDirect { get; set; }
         public Dictionary<string, string> ExcludeProviderIds { get; set; }

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

@@ -193,7 +193,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
             if (query.IncludeItemTypes.Length == 0)
             {
-                query.IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name };
+                query.IncludeItemTypes = new[] { typeof(Episode).Name };
             }
             query.IsVirtualItem = false;
             query.Limit = 0;

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

@@ -82,7 +82,7 @@ namespace MediaBrowser.Controller.Entities
 
         public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
         {
-            var result = GetItems(new InternalItemsQuery
+            var result = GetItemList(new InternalItemsQuery
             {
                 User = user,
                 EnableTotalRecordCount = false,
@@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Entities
 
             });
 
-            return result.Items;
+            return result;
         }
 
         public override bool CanDelete()
@@ -105,7 +105,7 @@ namespace MediaBrowser.Controller.Entities
 
         public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
         {
-            var result = GetItems(new InternalItemsQuery
+            var result = GetItemList(new InternalItemsQuery
             {
                 User = user,
                 Recursive = true,
@@ -117,7 +117,7 @@ namespace MediaBrowser.Controller.Entities
 
             });
 
-            return result.Items.Where(i => UserViewBuilder.FilterItem(i, query));
+            return result.Where(i => UserViewBuilder.FilterItem(i, query));
         }
 
         protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)

+ 1 - 4
MediaBrowser.Controller/Playlists/Playlist.cs

@@ -182,10 +182,7 @@ namespace MediaBrowser.Controller.Playlists
                     DtoOptions = options
                 };
 
-                var itemsResult = folder.GetItems(query);
-                var items = itemsResult.Items;
-
-                return items;
+                return folder.GetItemList(query);
             }
 
             return new[] { item };

+ 2 - 1
MediaBrowser.Model/Querying/ItemFields.cs

@@ -224,6 +224,7 @@
         SeriesPresentationUniqueKey,
         DateLastRefreshed,
         DateLastSaved,
-        RefreshState
+        RefreshState,
+        ChannelImage
     }
 }

+ 1 - 1
MediaBrowser.Server.Mono/Program.cs

@@ -97,7 +97,7 @@ namespace MediaBrowser.Server.Mono
         private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, StartupOptions options)
         {
             // Allow all https requests
-            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
+            //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
 
             var environmentInfo = GetEnvironmentInfo();