فهرست منبع

Merge pull request #2864 from MediaBrowser/dev

Dev
Luke 7 سال پیش
والد
کامیت
637795aaf5
51فایلهای تغییر یافته به همراه326 افزوده شده و 273 حذف شده
  1. 8 10
      Emby.Dlna/ContentDirectory/ControlHandler.cs
  2. 7 6
      Emby.Server.Implementations/Channels/ChannelManager.cs
  3. 3 15
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  4. 3 0
      Emby.Server.Implementations/HttpServer/FileWriter.cs
  5. 21 10
      Emby.Server.Implementations/HttpServer/Security/AuthService.cs
  6. 4 5
      Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
  7. 5 7
      Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
  8. 32 2
      Emby.Server.Implementations/Library/LibraryManager.cs
  9. 2 1
      Emby.Server.Implementations/Library/MusicManager.cs
  10. 2 1
      Emby.Server.Implementations/Library/SearchEngine.cs
  11. 1 2
      Emby.Server.Implementations/Library/UserViewManager.cs
  12. 1 2
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  13. 17 17
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  14. 4 3
      Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
  15. 1 1
      Emby.Server.Implementations/ServerManager/WebSocketConnection.cs
  16. 4 8
      Emby.Server.Implementations/TV/TVSeriesManager.cs
  17. 39 22
      Emby.Server.Implementations/TextEncoding/TextEncoding.cs
  18. 1 1
      Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
  19. 12 3
      MediaBrowser.Api/ChannelService.cs
  20. 1 3
      MediaBrowser.Api/Images/ImageService.cs
  21. 3 3
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  22. 2 4
      MediaBrowser.Api/Movies/MoviesService.cs
  23. 1 2
      MediaBrowser.Api/Reports/ReportsService.cs
  24. 3 1
      MediaBrowser.Api/SuggestionsService.cs
  25. 9 4
      MediaBrowser.Api/System/SystemService.cs
  26. 1 2
      MediaBrowser.Api/TvShowsService.cs
  27. 1 1
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  28. 30 5
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  29. 1 2
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  30. 1 2
      MediaBrowser.Controller/Channels/Channel.cs
  31. 3 4
      MediaBrowser.Controller/Entities/Folder.cs
  32. 2 7
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  33. 6 6
      MediaBrowser.Controller/Entities/TV/Series.cs
  34. 7 11
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  35. 2 2
      MediaBrowser.Controller/Library/ILibraryManager.cs
  36. 0 2
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  37. 4 3
      MediaBrowser.Controller/Net/AuthenticatedAttribute.cs
  38. 3 3
      MediaBrowser.Controller/Net/IAuthService.cs
  39. 3 2
      MediaBrowser.Controller/Net/IAuthorizationContext.cs
  40. 0 14
      MediaBrowser.Controller/Net/IServiceRequest.cs
  41. 3 2
      MediaBrowser.Controller/Net/ISessionContext.cs
  42. 1 3
      MediaBrowser.Controller/Net/LoggedAttribute.cs
  43. 0 42
      MediaBrowser.Controller/Net/ServiceRequest.cs
  44. 3 5
      MediaBrowser.Controller/Playlists/Playlist.cs
  45. 5 4
      MediaBrowser.Model/Channels/ChannelItemQuery.cs
  46. 2 12
      MediaBrowser.Model/LiveTv/ProgramQuery.cs
  47. 2 2
      MediaBrowser.Model/Text/ITextEncoding.cs
  48. 1 1
      Nuget/MediaBrowser.Common.nuspec
  49. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec
  50. 1 1
      SharedVersion.cs
  51. 56 0
      SocketHttpListener/Net/HttpResponseStream.Managed.cs

+ 8 - 10
Emby.Dlna/ContentDirectory/ControlHandler.cs

@@ -458,8 +458,7 @@ namespace Emby.Dlna.ContentDirectory
             {
                 Limit = limit,
                 StartIndex = startIndex,
-                SortBy = sortOrders.ToArray(sortOrders.Count),
-                SortOrder = sort.SortOrder,
+                OrderBy = sortOrders.Select(i => new Tuple<string, SortOrder>(i, sort.SortOrder)).ToArray(),
                 User = user,
                 Recursive = true,
                 IsMissing = false,
@@ -828,7 +827,7 @@ namespace Emby.Dlna.ContentDirectory
             query.Parent = parent;
             query.SetUser(user);
 
-            query.OrderBy = new List<Tuple<string, SortOrder>>
+            query.OrderBy = new Tuple<string, SortOrder>[]
             {
                 new Tuple<string, SortOrder> (ItemSortBy.DatePlayed, SortOrder.Descending),
                 new Tuple<string, SortOrder> (ItemSortBy.SortName, SortOrder.Ascending)
@@ -1077,7 +1076,7 @@ namespace Emby.Dlna.ContentDirectory
 
         private QueryResult<ServerItem> GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query)
         {
-            query.SortBy = new string[] { };
+            query.OrderBy = new Tuple<string, SortOrder>[] { };
 
             var items = _userViewManager.GetLatestItems(new LatestItemsQuery
             {
@@ -1094,7 +1093,7 @@ namespace Emby.Dlna.ContentDirectory
 
         private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query)
         {
-            query.SortBy = new string[] { };
+            query.OrderBy = new Tuple<string, SortOrder>[] { };
 
             var result = _tvSeriesManager.GetNextUp(new NextUpQuery
             {
@@ -1109,7 +1108,7 @@ namespace Emby.Dlna.ContentDirectory
 
         private QueryResult<ServerItem> GetTvLatest(BaseItem parent, User user, InternalItemsQuery query)
         {
-            query.SortBy = new string[] { };
+            query.OrderBy = new Tuple<string, SortOrder>[] { };
 
             var items = _userViewManager.GetLatestItems(new LatestItemsQuery
             {
@@ -1126,7 +1125,7 @@ namespace Emby.Dlna.ContentDirectory
 
         private QueryResult<ServerItem> GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query)
         {
-            query.SortBy = new string[] { };
+            query.OrderBy = new Tuple<string, SortOrder>[] { };
 
             var items = _userViewManager.GetLatestItems(new LatestItemsQuery
             {
@@ -1236,8 +1235,7 @@ namespace Emby.Dlna.ContentDirectory
                 sortOrders.Add(ItemSortBy.SortName);
             }
 
-            query.SortBy = sortOrders.ToArray(sortOrders.Count);
-            query.SortOrder = sort.SortOrder;
+            query.OrderBy = sortOrders.Select(i => new Tuple<string, SortOrder>(i, sort.SortOrder)).ToArray();
         }
 
         private QueryResult<ServerItem> GetItemsFromPerson(Person person, User user, int? startIndex, int? limit)
@@ -1246,7 +1244,7 @@ namespace Emby.Dlna.ContentDirectory
             {
                 PersonIds = new[] { person.Id.ToString("N") },
                 IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name, typeof(Trailer).Name },
-                SortBy = new[] { ItemSortBy.SortName },
+                OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
                 Limit = limit,
                 StartIndex = startIndex,
                 DtoOptions = GetDtoOptions()

+ 7 - 6
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -466,7 +466,7 @@ namespace Emby.Server.Implementations.Channels
             return _libraryManager.GetItemIds(new InternalItemsQuery
             {
                 IncludeItemTypes = new[] { typeof(Channel).Name },
-                SortBy = new[] { ItemSortBy.SortName }
+                OrderBy = new Tuple<string, SortOrder>[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }
 
             }).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray();
         }
@@ -932,14 +932,15 @@ namespace Emby.Server.Implementations.Channels
 
             ChannelItemSortField? sortField = null;
             ChannelItemSortField parsedField;
-            if (query.SortBy.Length == 1 &&
-                Enum.TryParse(query.SortBy[0], true, out parsedField))
+            var sortDescending = false;
+
+            if (query.OrderBy.Length == 1 &&
+                Enum.TryParse(query.OrderBy[0].Item1, true, out parsedField))
             {
                 sortField = parsedField;
+                sortDescending = query.OrderBy[0].Item2 == SortOrder.Descending;
             }
 
-            var sortDescending = query.SortOrder.HasValue && query.SortOrder.Value == SortOrder.Descending;
-
             var itemsResult = await GetChannelItems(channelProvider,
                 user,
                 query.FolderId,
@@ -1166,7 +1167,7 @@ namespace Emby.Server.Implementations.Channels
         {
             items = ApplyFilters(items, query.Filters, user);
 
-            items = _libraryManager.Sort(items, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending);
+            items = _libraryManager.Sort(items, user, query.OrderBy);
 
             var all = items.ToList();
             var totalCount = totalCountFromProvider ?? all.Count;

+ 3 - 15
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -2135,8 +2135,7 @@ namespace Emby.Server.Implementations.Data
                 //return true;
             }
 
-            var sortingFields = query.SortBy.ToList();
-            sortingFields.AddRange(query.OrderBy.Select(i => i.Item1));
+            var sortingFields = query.OrderBy.Select(i => i.Item1).ToList();
 
             if (sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase))
             {
@@ -2975,16 +2974,7 @@ namespace Emby.Server.Implementations.Data
         private string GetOrderByText(InternalItemsQuery query)
         {
             var orderBy = query.OrderBy.ToList();
-            var enableOrderInversion = true;
-
-            if (orderBy.Count == 0)
-            {
-                orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder)));
-            }
-            else
-            {
-                enableOrderInversion = false;
-            }
+            var enableOrderInversion = false;
 
             if (query.SimilarTo != null)
             {
@@ -2993,12 +2983,10 @@ namespace Emby.Server.Implementations.Data
                     orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
                     orderBy.Add(new Tuple<string, SortOrder>("SimilarityScore", SortOrder.Descending));
                     //orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
-                    query.SortOrder = SortOrder.Descending;
-                    enableOrderInversion = false;
                 }
             }
 
-            query.OrderBy = orderBy;
+            query.OrderBy = orderBy.ToArray();
 
             if (orderBy.Count == 0)
             {

+ 3 - 0
Emby.Server.Implementations/HttpServer/FileWriter.cs

@@ -160,6 +160,9 @@ namespace Emby.Server.Implementations.HttpServer
                 if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
                 {
                     Logger.Info("Transmit file {0}", Path);
+
+                    //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0;
+
                     await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
                     return;
                 }

+ 21 - 10
Emby.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using System;
 using System.Linq;
+using MediaBrowser.Model.Services;
 
 namespace Emby.Server.Implementations.HttpServer.Security
 {
@@ -37,19 +38,19 @@ namespace Emby.Server.Implementations.HttpServer.Security
         /// </summary>
         public string HtmlRedirect { get; set; }
 
-        public void Authenticate(IServiceRequest request,
+        public void Authenticate(IRequest request,
             IAuthenticationAttributes authAttribtues)
         {
             ValidateUser(request, authAttribtues);
         }
 
-        private void ValidateUser(IServiceRequest request,
+        private void ValidateUser(IRequest request,
             IAuthenticationAttributes authAttribtues)
         {
             // This code is executed before the service
             var auth = AuthorizationContext.GetAuthorizationInfo(request);
 
-            if (!IsExemptFromAuthenticationToken(auth, authAttribtues))
+            if (!IsExemptFromAuthenticationToken(auth, authAttribtues, request))
             {
                 var valid = IsValidConnectKey(auth.Token);
 
@@ -75,7 +76,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
             var info = GetTokenInfo(request);
 
-            if (!IsExemptFromRoles(auth, authAttribtues, info))
+            if (!IsExemptFromRoles(auth, authAttribtues, request, info))
             {
                 var roles = authAttribtues.GetRoles();
 
@@ -95,7 +96,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             }
         }
 
-        private void ValidateUserAccess(User user, IServiceRequest request,
+        private void ValidateUserAccess(User user, IRequest request,
             IAuthenticationAttributes authAttribtues,
             AuthorizationInfo auth)
         {
@@ -111,7 +112,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
                 !authAttribtues.EscapeParentalControl &&
                 !user.IsParentalScheduleAllowed())
             {
-                request.AddResponseHeader("X-Application-Error-Code", "ParentalControl");
+                request.Response.AddHeader("X-Application-Error-Code", "ParentalControl");
 
                 throw new SecurityException("This user account is not allowed access at this time.")
                 {
@@ -131,23 +132,33 @@ namespace Emby.Server.Implementations.HttpServer.Security
             }
         }
 
-        private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
+        private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request)
         {
             if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
             {
                 return true;
             }
 
+            if (authAttribtues.AllowLocal && request.IsLocal)
+            {
+                return true;
+            }
+
             return false;
         }
 
-        private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo)
+        private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request, AuthenticationInfo tokenInfo)
         {
             if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
             {
                 return true;
             }
 
+            if (authAttribtues.AllowLocal && request.IsLocal)
+            {
+                return true;
+            }
+
             if (string.IsNullOrWhiteSpace(auth.Token))
             {
                 return true;
@@ -195,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             }
         }
 
-        private AuthenticationInfo GetTokenInfo(IServiceRequest request)
+        private AuthenticationInfo GetTokenInfo(IRequest request)
         {
             object info;
             request.Items.TryGetValue("OriginalAuthenticationInfo", out info);
@@ -212,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             return ConnectManager.IsAuthorizationTokenValid(token);
         }
 
-        private void ValidateSecurityToken(IServiceRequest request, string token)
+        private void ValidateSecurityToken(IRequest request, string token)
         {
             if (string.IsNullOrWhiteSpace(token))
             {

+ 4 - 5
Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs

@@ -21,11 +21,10 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
         public AuthorizationInfo GetAuthorizationInfo(object requestContext)
         {
-            var req = new ServiceRequest((IRequest)requestContext);
-            return GetAuthorizationInfo(req);
+            return GetAuthorizationInfo((IRequest)requestContext);
         }
 
-        public AuthorizationInfo GetAuthorizationInfo(IServiceRequest requestContext)
+        public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext)
         {
             object cached;
             if (requestContext.Items.TryGetValue("AuthorizationInfo", out cached))
@@ -41,7 +40,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
         /// </summary>
         /// <param name="httpReq">The HTTP req.</param>
         /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        private AuthorizationInfo GetAuthorization(IServiceRequest httpReq)
+        private AuthorizationInfo GetAuthorization(IRequest httpReq)
         {
             var auth = GetAuthorizationDictionary(httpReq);
 
@@ -135,7 +134,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
         /// </summary>
         /// <param name="httpReq">The HTTP req.</param>
         /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        private Dictionary<string, string> GetAuthorizationDictionary(IServiceRequest httpReq)
+        private Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq)
         {
             var auth = httpReq.Headers["X-Emby-Authorization"];
 

+ 5 - 7
Emby.Server.Implementations/HttpServer/Security/SessionContext.cs

@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             _sessionManager = sessionManager;
         }
 
-        public Task<SessionInfo> GetSession(IServiceRequest requestContext)
+        public Task<SessionInfo> GetSession(IRequest requestContext)
         {
             var authorization = _authContext.GetAuthorizationInfo(requestContext);
 
@@ -38,7 +38,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
             return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.RemoteIp, user);
         }
 
-        private AuthenticationInfo GetTokenInfo(IServiceRequest request)
+        private AuthenticationInfo GetTokenInfo(IRequest request)
         {
             object info;
             request.Items.TryGetValue("OriginalAuthenticationInfo", out info);
@@ -47,11 +47,10 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
         public Task<SessionInfo> GetSession(object requestContext)
         {
-            var req = new ServiceRequest((IRequest)requestContext);
-            return GetSession(req);
+            return GetSession((IRequest)requestContext);
         }
 
-        public async Task<User> GetUser(IServiceRequest requestContext)
+        public async Task<User> GetUser(IRequest requestContext)
         {
             var session = await GetSession(requestContext).ConfigureAwait(false);
 
@@ -60,8 +59,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
         public Task<User> GetUser(object requestContext)
         {
-            var req = new ServiceRequest((IRequest)requestContext);
-            return GetUser(req);
+            return GetUser((IRequest)requestContext);
         }
     }
 }

+ 32 - 2
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -846,8 +846,7 @@ namespace Emby.Server.Implementations.Library
             {
                 Path = path,
                 IsFolder = isFolder,
-                SortBy = new[] { ItemSortBy.DateCreated },
-                SortOrder = SortOrder.Descending,
+                OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
                 Limit = 1,
                 DtoOptions = new DtoOptions(true)
             };
@@ -1777,6 +1776,37 @@ namespace Emby.Server.Implementations.Library
             return orderedItems ?? items;
         }
 
+        public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<Tuple<string, SortOrder>> orderByList)
+        {
+            var isFirst = true;
+
+            IOrderedEnumerable<BaseItem> orderedItems = null;
+
+            foreach (var orderBy in orderByList)
+            {
+                var comparer = GetComparer(orderBy.Item1, user);
+                if (comparer == null)
+                {
+                    continue;
+                }
+
+                var sortOrder = orderBy.Item2;
+
+                if (isFirst)
+                {
+                    orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer);
+                }
+                else
+                {
+                    orderedItems = sortOrder == SortOrder.Descending ? orderedItems.ThenByDescending(i => i, comparer) : orderedItems.ThenBy(i => i, comparer);
+                }
+
+                isFirst = false;
+            }
+
+            return orderedItems ?? items;
+        }
+
         /// <summary>
         /// Gets the comparer.
         /// </summary>

+ 2 - 1
Emby.Server.Implementations/Library/MusicManager.cs

@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using MediaBrowser.Controller.Dto;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 
 namespace Emby.Server.Implementations.Library
@@ -88,7 +89,7 @@ namespace Emby.Server.Implementations.Library
 
                 Limit = 200,
 
-                SortBy = new[] { ItemSortBy.Random },
+                OrderBy = new [] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) },
 
                 DtoOptions = dtoOptions
 

+ 2 - 1
Emby.Server.Implementations/Library/SearchEngine.cs

@@ -10,6 +10,7 @@ using System.Linq;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Extensions;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Extensions;
 
 namespace Emby.Server.Implementations.Library
@@ -169,7 +170,7 @@ namespace Emby.Server.Implementations.Library
                 Limit = query.Limit,
                 IncludeItemsByName = string.IsNullOrWhiteSpace(query.ParentId),
                 ParentId = string.IsNullOrWhiteSpace(query.ParentId) ? (Guid?)null : new Guid(query.ParentId),
-                SortBy = new[] { ItemSortBy.SortName },
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) },
                 Recursive = true,
 
                 IsKids = query.IsKids,

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

@@ -319,8 +319,7 @@ namespace Emby.Server.Implementations.Library
             var query = new InternalItemsQuery(user)
             {
                 IncludeItemTypes = includeItemTypes,
-                SortOrder = SortOrder.Descending,
-                SortBy = new[] { ItemSortBy.DateCreated },
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
                 IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
                 ExcludeItemTypes = excludeItemTypes,
                 IsVirtualItem = false,

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

@@ -1687,8 +1687,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
                 var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery
                 {
-                    SortBy = new[] { ItemSortBy.DateCreated },
-                    SortOrder = SortOrder.Descending,
+                    OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
                     IsVirtualItem = false,
                     IsFolder = false,
                     Recursive = true,

+ 17 - 17
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -187,7 +187,6 @@ namespace Emby.Server.Implementations.LiveTv
                 IsSports = query.IsSports,
                 IsSeries = query.IsSeries,
                 IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
-                SortOrder = query.SortOrder ?? SortOrder.Ascending,
                 TopParentIds = new[] { topFolder.Id.ToString("N") },
                 IsFavorite = query.IsFavorite,
                 IsLiked = query.IsLiked,
@@ -196,18 +195,22 @@ namespace Emby.Server.Implementations.LiveTv
                 DtoOptions = dtoOptions
             };
 
-            internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending)));
+            var orderBy = internalQuery.OrderBy.ToList();
+
+            orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending)));
 
             if (query.EnableFavoriteSorting)
             {
-                internalQuery.OrderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
+                orderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
             }
 
             if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
             {
-                internalQuery.OrderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
+                orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
             }
 
+            internalQuery.OrderBy = orderBy.ToArray();
+
             return _libraryManager.GetItemsResult(internalQuery);
         }
 
@@ -918,10 +921,10 @@ namespace Emby.Server.Implementations.LiveTv
 
             var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
 
-            if (query.SortBy.Length == 0)
+            if (query.OrderBy.Length == 0)
             {
                 // Unless something else was specified, order by start date to take advantage of a specialized index
-                query.SortBy = new[] { ItemSortBy.StartDate };
+                query.OrderBy = new Tuple<string, SortOrder>[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) };
             }
 
             RemoveFields(options);
@@ -942,8 +945,7 @@ namespace Emby.Server.Implementations.LiveTv
                 Genres = query.Genres,
                 StartIndex = query.StartIndex,
                 Limit = query.Limit,
-                SortBy = query.SortBy,
-                SortOrder = query.SortOrder ?? SortOrder.Ascending,
+                OrderBy = query.OrderBy,
                 EnableTotalRecordCount = query.EnableTotalRecordCount,
                 TopParentIds = new[] { topFolder.Id.ToString("N") },
                 Name = query.Name,
@@ -1012,7 +1014,7 @@ namespace Emby.Server.Implementations.LiveTv
                 IsSports = query.IsSports,
                 IsKids = query.IsKids,
                 EnableTotalRecordCount = query.EnableTotalRecordCount,
-                SortBy = new[] { ItemSortBy.StartDate },
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) },
                 TopParentIds = new[] { topFolder.Id.ToString("N") },
                 DtoOptions = options
             };
@@ -1644,8 +1646,7 @@ namespace Emby.Server.Implementations.LiveTv
                 IsVirtualItem = false,
                 Limit = query.Limit,
                 StartIndex = query.StartIndex,
-                SortBy = new[] { ItemSortBy.DateCreated },
-                SortOrder = SortOrder.Descending,
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
                 EnableTotalRecordCount = query.EnableTotalRecordCount,
                 IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count),
                 ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count),
@@ -1692,8 +1693,7 @@ namespace Emby.Server.Implementations.LiveTv
                 Recursive = true,
                 AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(folders.Count),
                 Limit = query.Limit,
-                SortBy = new[] { ItemSortBy.DateCreated },
-                SortOrder = SortOrder.Descending,
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
                 EnableTotalRecordCount = query.EnableTotalRecordCount,
                 IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count),
                 ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count),
@@ -1927,11 +1927,11 @@ namespace Emby.Server.Implementations.LiveTv
 
             var info = recording;
 
-            dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
+            dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) || service == null
                 ? null
                 : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
 
-            dto.TimerId = string.IsNullOrEmpty(info.TimerId)
+            dto.TimerId = string.IsNullOrEmpty(info.TimerId) || service == null
                 ? null
                 : _tvDtoService.GetInternalTimerId(service.Name, info.TimerId).ToString("N");
 
@@ -2037,7 +2037,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false);
 
-            var returnArray =  _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
+            var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
 
             return new QueryResult<BaseItemDto>
             {
@@ -2377,7 +2377,7 @@ namespace Emby.Server.Implementations.LiveTv
                 MaxStartDate = now,
                 MinEndDate = now,
                 Limit = channelIds.Length,
-                SortBy = new[] { "StartDate" },
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) },
                 TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") },
                 DtoOptions = options
 

+ 4 - 3
Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Configuration;
+using System;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -86,7 +87,7 @@ namespace Emby.Server.Implementations.Playlists
             {
                 Genres = new[] { item.Name },
                 IncludeItemTypes = new[] { typeof(MusicAlbum).Name, typeof(MusicVideo).Name, typeof(Audio).Name },
-                SortBy = new[] { ItemSortBy.Random },
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) },
                 Limit = 4,
                 Recursive = true,
                 ImageTypes = new[] { ImageType.Primary },
@@ -118,7 +119,7 @@ namespace Emby.Server.Implementations.Playlists
             {
                 Genres = new[] { item.Name },
                 IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name },
-                SortBy = new[] { ItemSortBy.Random },
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) },
                 Limit = 4,
                 Recursive = true,
                 ImageTypes = new[] { ImageType.Primary },

+ 1 - 1
Emby.Server.Implementations/ServerManager/WebSocketConnection.cs

@@ -136,7 +136,7 @@ namespace Emby.Server.Implementations.ServerManager
                 return;
             }
 
-            var charset = _textEncoding.GetDetectedEncodingName(bytes, null, false);
+            var charset = _textEncoding.GetDetectedEncodingName(bytes, bytes.Length, null, false);
 
             if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase))
             {

+ 4 - 8
Emby.Server.Implementations/TV/TVSeriesManager.cs

@@ -64,8 +64,7 @@ namespace Emby.Server.Implementations.TV
             var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
                 IncludeItemTypes = new[] { typeof(Episode).Name },
-                SortBy = new[] { ItemSortBy.DatePlayed },
-                SortOrder = SortOrder.Descending,
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
                 SeriesPresentationUniqueKey = presentationUniqueKey,
                 Limit = limit,
                 ParentId = parentIdGuid,
@@ -122,8 +121,7 @@ namespace Emby.Server.Implementations.TV
             var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
                 IncludeItemTypes = new[] { typeof(Episode).Name },
-                SortBy = new[] { ItemSortBy.DatePlayed },
-                SortOrder = SortOrder.Descending,
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
                 SeriesPresentationUniqueKey = presentationUniqueKey,
                 Limit = limit,
                 DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions
@@ -200,8 +198,7 @@ namespace Emby.Server.Implementations.TV
                 AncestorWithPresentationUniqueKey = null,
                 SeriesPresentationUniqueKey = seriesKey,
                 IncludeItemTypes = new[] { typeof(Episode).Name },
-                SortBy = new[] { ItemSortBy.SortName },
-                SortOrder = SortOrder.Descending,
+                OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) },
                 IsPlayed = true,
                 Limit = 1,
                 ParentIndexNumberNotEquals = 0,
@@ -223,8 +220,7 @@ namespace Emby.Server.Implementations.TV
                     AncestorWithPresentationUniqueKey = null,
                     SeriesPresentationUniqueKey = seriesKey,
                     IncludeItemTypes = new[] { typeof(Episode).Name },
-                    SortBy = new[] { ItemSortBy.SortName },
-                    SortOrder = SortOrder.Ascending,
+                    OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) },
                     Limit = 1,
                     IsPlayed = false,
                     IsVirtualItem = false,

+ 39 - 22
Emby.Server.Implementations/TextEncoding/TextEncoding.cs

@@ -27,18 +27,33 @@ namespace Emby.Server.Implementations.TextEncoding
             return Encoding.ASCII;
         }
 
-        private Encoding GetInitialEncoding(byte[] buffer)
+        private Encoding GetInitialEncoding(byte[] buffer, int count)
         {
-            if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
-                return Encoding.UTF8;
-            if (buffer[0] == 0xfe && buffer[1] == 0xff)
-                return Encoding.Unicode;
-            if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
-                return Encoding.UTF32;
-            if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
-                return Encoding.UTF7;
+            if (count >= 3)
+            {
+                if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
+                    return Encoding.UTF8;
+            }
+
+            if (count >= 2)
+            {
+                if (buffer[0] == 0xfe && buffer[1] == 0xff)
+                    return Encoding.Unicode;
+            }
+
+            if (count >= 4)
+            {
+                if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
+                    return Encoding.UTF32;
+            }
 
-            var result = new TextEncodingDetect().DetectEncoding(buffer, buffer.Length);
+            if (count >= 3)
+            {
+                if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
+                    return Encoding.UTF7;
+            }
+
+            var result = new TextEncodingDetect().DetectEncoding(buffer, count);
 
             switch (result)
             {
@@ -64,9 +79,11 @@ namespace Emby.Server.Implementations.TextEncoding
         }
 
         private bool _langDetectInitialized;
-        public string GetDetectedEncodingName(byte[] bytes, string language, bool enableLanguageDetection)
+        public string GetDetectedEncodingName(byte[] bytes, int count, string language, bool enableLanguageDetection)
         {
-            var encoding = GetInitialEncoding(bytes);
+            var index = 0;
+
+            var encoding = GetInitialEncoding(bytes, count);
 
             if (encoding != null && encoding.Equals(Encoding.UTF8))
             {
@@ -81,7 +98,7 @@ namespace Emby.Server.Implementations.TextEncoding
                     LanguageDetector.Initialize(_json);
                 }
 
-                language = DetectLanguage(bytes);
+                language = DetectLanguage(bytes, index, count);
 
                 if (!string.IsNullOrWhiteSpace(language))
                 {
@@ -89,7 +106,7 @@ namespace Emby.Server.Implementations.TextEncoding
                 }
             }
 
-            var charset = DetectCharset(bytes, language);
+            var charset = DetectCharset(bytes, index, count, language);
 
             if (!string.IsNullOrWhiteSpace(charset))
             {
@@ -112,11 +129,11 @@ namespace Emby.Server.Implementations.TextEncoding
             return null;
         }
 
-        private string DetectLanguage(byte[] bytes)
+        private string DetectLanguage(byte[] bytes, int index, int count)
         {
             try
             {
-                return LanguageDetector.DetectLanguage(Encoding.UTF8.GetString(bytes));
+                return LanguageDetector.DetectLanguage(Encoding.UTF8.GetString(bytes, index, count));
             }
             catch (NLangDetectException ex)
             {
@@ -124,7 +141,7 @@ namespace Emby.Server.Implementations.TextEncoding
 
             try
             {
-                return LanguageDetector.DetectLanguage(Encoding.ASCII.GetString(bytes));
+                return LanguageDetector.DetectLanguage(Encoding.ASCII.GetString(bytes, index, count));
             }
             catch (NLangDetectException ex)
             {
@@ -132,7 +149,7 @@ namespace Emby.Server.Implementations.TextEncoding
 
             try
             {
-                return LanguageDetector.DetectLanguage(Encoding.Unicode.GetString(bytes));
+                return LanguageDetector.DetectLanguage(Encoding.Unicode.GetString(bytes, index, count));
             }
             catch (NLangDetectException ex)
             {
@@ -163,9 +180,9 @@ namespace Emby.Server.Implementations.TextEncoding
             }
         }
 
-        public Encoding GetDetectedEncoding(byte[] bytes, string language, bool enableLanguageDetection)
+        public Encoding GetDetectedEncoding(byte[] bytes, int size, string language, bool enableLanguageDetection)
         {
-            var charset = GetDetectedEncodingName(bytes, language, enableLanguageDetection);
+            var charset = GetDetectedEncodingName(bytes, size, language, enableLanguageDetection);
 
             return GetEncodingFromCharset(charset);
         }
@@ -225,10 +242,10 @@ namespace Emby.Server.Implementations.TextEncoding
             }
         }
 
-        private string DetectCharset(byte[] bytes, string language)
+        private string DetectCharset(byte[] bytes, int index, int count, string language)
         {
             var detector = new CharsetDetector();
-            detector.Feed(bytes, 0, bytes.Length);
+            detector.Feed(bytes, index, count);
             detector.DataEnd();
 
             var charset = detector.Charset;

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

@@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.UserViews
                 Recursive = recursive,
                 IncludeItemTypes = new[] { typeof(BoxSet).Name },
                 Limit = 20,
-                SortBy = new[] { ItemSortBy.Random },
+                OrderBy = new [] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) },
                 DtoOptions = new DtoOptions(false)
             });
 

+ 12 - 3
MediaBrowser.Api/ChannelService.cs

@@ -9,6 +9,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Api.UserLibrary;
 using MediaBrowser.Model.Services;
 
 namespace MediaBrowser.Api
@@ -90,7 +91,7 @@ namespace MediaBrowser.Api
         public int? Limit { get; set; }
 
         [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public SortOrder? SortOrder { get; set; }
+        public string SortOrder { get; set; }
 
         [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Filters { get; set; }
@@ -116,6 +117,15 @@ namespace MediaBrowser.Api
 
             return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
         }
+
+        /// <summary>
+        /// Gets the order by.
+        /// </summary>
+        /// <returns>IEnumerable{ItemSortBy}.</returns>
+        public Tuple<string, SortOrder>[] GetOrderBy()
+        {
+            return BaseItemsRequest.GetOrderBy(SortBy, SortOrder);
+        }
     }
 
     [Route("/Channels/Items/Latest", "GET", Summary = "Gets channel items")]
@@ -228,8 +238,7 @@ namespace MediaBrowser.Api
                 UserId = request.UserId,
                 ChannelId = request.Id,
                 FolderId = request.FolderId,
-                SortOrder = request.SortOrder,
-                SortBy = (request.SortBy ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
+                OrderBy = request.GetOrderBy(),
                 Filters = request.GetFilters().ToArray(),
                 Fields = request.GetItemFields()
 

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

@@ -639,9 +639,7 @@ namespace MediaBrowser.Api.Images
                 IsHeadRequest = isHeadRequest,
                 Path = imageResult.Item1,
 
-                // Sometimes imagemagick keeps a hold on the file briefly even after it's done writing to it.
-                // I'd rather do this than add a delay after saving the file
-                FileShare = FileShareMode.ReadWrite
+                FileShare = FileShareMode.Read
 
             }).ConfigureAwait(false);
         }

+ 3 - 3
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -15,6 +15,7 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Api.UserLibrary;
 using MediaBrowser.Model.IO;
 
 using MediaBrowser.Controller.Configuration;
@@ -373,7 +374,7 @@ namespace MediaBrowser.Api.LiveTv
         public string SortBy { get; set; }
 
         [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public SortOrder? SortOrder { get; set; }
+        public string SortOrder { get; set; }
 
         [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; }
@@ -994,8 +995,7 @@ namespace MediaBrowser.Api.LiveTv
 
             query.StartIndex = request.StartIndex;
             query.Limit = request.Limit;
-            query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
-            query.SortOrder = request.SortOrder;
+            query.OrderBy = BaseItemsRequest.GetOrderBy(request.SortBy, request.SortOrder);
             query.IsNews = request.IsNews;
             query.IsMovie = request.IsMovie;
             query.IsSeries = request.IsSeries;

+ 2 - 4
MediaBrowser.Api/Movies/MoviesService.cs

@@ -193,8 +193,7 @@ namespace MediaBrowser.Api.Movies
                     //typeof(LiveTvProgram).Name
                 },
                 // IsMovie = true
-                SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random },
-                SortOrder = SortOrder.Descending,
+                OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
                 Limit = 7,
                 ParentId = parentIdGuid,
                 Recursive = true,
@@ -215,8 +214,7 @@ namespace MediaBrowser.Api.Movies
             {
                 IncludeItemTypes = itemTypes.ToArray(itemTypes.Count),
                 IsMovie = true,
-                SortBy = new[] { ItemSortBy.Random },
-                SortOrder = SortOrder.Descending,
+                OrderBy = new[] { ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
                 Limit = 10,
                 IsFavoriteOrLiked = true,
                 ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(recentlyPlayedMovies.Count),

+ 1 - 2
MediaBrowser.Api/Reports/ReportsService.cs

@@ -176,8 +176,7 @@ namespace MediaBrowser.Api.Reports
                 IncludeItemTypes = request.GetIncludeItemTypes(),
                 ExcludeItemTypes = request.GetExcludeItemTypes(),
                 Recursive = request.Recursive,
-                SortBy = request.GetOrderBy(),
-                SortOrder = request.SortOrder ?? SortOrder.Ascending,
+                OrderBy = request.GetOrderBy(),
 
                 IsFavorite = request.IsFavorite,
                 Limit = request.Limit,

+ 3 - 1
MediaBrowser.Api/SuggestionsService.cs

@@ -5,8 +5,10 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Services;
 using System;
+using System.Linq;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Extensions;
 
 namespace MediaBrowser.Api
@@ -79,7 +81,7 @@ namespace MediaBrowser.Api
         {
             return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
             {
-                SortBy = new string[] { ItemSortBy.Random },
+                OrderBy = new[] { ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
                 MediaTypes = request.GetMediaTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
                 IsVirtualItem = false,

+ 9 - 4
MediaBrowser.Api/System/SystemService.cs

@@ -43,7 +43,7 @@ namespace MediaBrowser.Api.System
     /// Class RestartApplication
     /// </summary>
     [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")]
-    [Authenticated(Roles = "Admin")]
+    [Authenticated(Roles = "Admin", AllowLocal = true)]
     public class RestartApplication
     {
     }
@@ -52,10 +52,9 @@ namespace MediaBrowser.Api.System
     /// This is currently not authenticated because the uninstaller needs to be able to shutdown the server.
     /// </summary>
     [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")]
+    [Authenticated(Roles = "Admin", AllowLocal = true)]
     public class ShutdownApplication
     {
-        // TODO: This is not currently authenticated due to uninstaller
-        // Improve later
     }
 
     [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")]
@@ -126,7 +125,7 @@ namespace MediaBrowser.Api.System
             }
             catch (IOException)
             {
-                files = new FileSystemMetadata[]{};
+                files = new FileSystemMetadata[] { };
             }
 
             var result = files.Select(i => new LogFile
@@ -149,6 +148,12 @@ namespace MediaBrowser.Api.System
             var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
                 .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
 
+            // For older files, assume fully static
+            if (file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1))
+            {
+                return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShareMode.Read);
+            }
+
             return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShareMode.ReadWrite);
         }
 

+ 1 - 2
MediaBrowser.Api/TvShowsService.cs

@@ -347,8 +347,7 @@ namespace MediaBrowser.Api
             var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
                 IncludeItemTypes = new[] { typeof(Episode).Name },
-                SortBy = new[] { "PremiereDate", "AirTime", "SortName" },
-                SortOrder = SortOrder.Ascending,
+                OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
                 MinPremiereDate = minPremiereDate,
                 StartIndex = request.StartIndex,
                 Limit = request.Limit,

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

@@ -299,7 +299,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             var filteredItems = FilterItems(request, extractedItems, user);
 
-            filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending);
+            filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy());
 
             var ibnItemsArray = filteredItems.ToList();
 

+ 30 - 5
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -136,7 +136,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// </summary>
         /// <value>The sort order.</value>
         [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public SortOrder? SortOrder { get; set; }
+        public string SortOrder { get; set; }
 
         /// <summary>
         /// Specify this to localize the search to a specific item or folder. Omit to use the root.
@@ -467,16 +467,41 @@ namespace MediaBrowser.Api.UserLibrary
         /// Gets the order by.
         /// </summary>
         /// <returns>IEnumerable{ItemSortBy}.</returns>
-        public string[] GetOrderBy()
+        public Tuple<string, SortOrder>[] GetOrderBy()
         {
-            var val = SortBy;
+            return GetOrderBy(SortBy, SortOrder);
+        }
+
+        public static Tuple<string, SortOrder>[] GetOrderBy(string sortBy, string requestedSortOrder)
+        {
+            var val = sortBy;
 
             if (string.IsNullOrEmpty(val))
             {
-                return new string[] { };
+                return new Tuple<string, Model.Entities.SortOrder>[] { };
+            }
+
+            var vals = val.Split(',');
+            if (string.IsNullOrWhiteSpace(requestedSortOrder))
+            {
+                requestedSortOrder = "Ascending";
+            }
+
+            var sortOrders = requestedSortOrder.Split(',');
+
+            var result = new Tuple<string, Model.Entities.SortOrder>[vals.Length];
+
+            for (var i = 0; i < vals.Length; i++)
+            {
+                var sortOrderIndex = sortOrders.Length > i ? i : 0;
+
+                var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
+                var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending;
+
+                result[i] = new Tuple<string, Model.Entities.SortOrder>(vals[i], sortOrder);
             }
 
-            return val.Split(',');
+            return result;
         }
     }
 }

+ 1 - 2
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -198,8 +198,7 @@ namespace MediaBrowser.Api.UserLibrary
                 IncludeItemTypes = request.GetIncludeItemTypes(),
                 ExcludeItemTypes = request.GetExcludeItemTypes(),
                 Recursive = request.Recursive,
-                SortBy = request.GetOrderBy(),
-                SortOrder = request.SortOrder ?? SortOrder.Ascending,
+                OrderBy = request.GetOrderBy(),
 
                 IsFavorite = request.IsFavorite,
                 Limit = request.Limit,

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

@@ -58,8 +58,7 @@ namespace MediaBrowser.Controller.Channels
                     Limit = query.Limit,
                     StartIndex = query.StartIndex,
                     UserId = query.User.Id.ToString("N"),
-                    SortBy = query.SortBy,
-                    SortOrder = query.SortOrder
+                    OrderBy = query.OrderBy
 
                 }, new SimpleProgress<double>(), CancellationToken.None).Result;
             }

+ 3 - 4
MediaBrowser.Controller/Entities/Folder.cs

@@ -952,7 +952,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 var result = LibraryManager.GetItemsResult(query);
 
-                if (query.SortBy.Length == 0)
+                if (query.OrderBy.Length == 0)
                 {
                     var ids = query.ItemIds.ToList();
 
@@ -973,7 +973,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 var result = LibraryManager.GetItemList(query);
 
-                if (query.SortBy.Length == 0)
+                if (query.OrderBy.Length == 0)
                 {
                     var ids = query.ItemIds.ToList();
 
@@ -1000,8 +1000,7 @@ namespace MediaBrowser.Controller.Entities
                         Limit = query.Limit,
                         StartIndex = query.StartIndex,
                         UserId = query.User.Id.ToString("N"),
-                        SortBy = query.SortBy,
-                        SortOrder = query.SortOrder
+                        OrderBy = query.OrderBy
 
                     }, new SimpleProgress<double>(), CancellationToken.None).Result;
                 }

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

@@ -16,10 +16,6 @@ namespace MediaBrowser.Controller.Entities
 
         public int? Limit { get; set; }
 
-        public string[] SortBy { get; set; }
-
-        public SortOrder SortOrder { get; set; }
-
         public User User { get; set; }
 
         public BaseItem SimilarTo { get; set; }
@@ -165,7 +161,7 @@ namespace MediaBrowser.Controller.Entities
         public Dictionary<string, string> ExcludeProviderIds { get; set; }
         public bool EnableGroupByMetadataKey { get; set; }
 
-        public List<Tuple<string, SortOrder>> OrderBy { get; set; }
+        public Tuple<string, SortOrder>[] OrderBy { get; set; }
 
         public DateTime? MinDateCreated { get; set; }
         public DateTime? MinDateLastSaved { get; set; }
@@ -190,7 +186,6 @@ namespace MediaBrowser.Controller.Entities
             BlockUnratedItems = new UnratedItem[] { };
             Tags = new string[] { };
             OfficialRatings = new string[] { };
-            SortBy = new string[] { };
             MediaTypes = new string[] { };
             IncludeItemTypes = new string[] { };
             ExcludeItemTypes = new string[] { };
@@ -213,7 +208,7 @@ namespace MediaBrowser.Controller.Entities
             TrailerTypes = new TrailerType[] { };
             SourceTypes = new SourceType[] { };
             SeriesStatuses = new SeriesStatus[] { };
-            OrderBy = new List<Tuple<string, SortOrder>>();
+            OrderBy = new Tuple<string, SortOrder>[] { };
         }
 
         public InternalItemsQuery(User user)

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

@@ -252,7 +252,7 @@ namespace MediaBrowser.Controller.Entities.TV
             query.AncestorWithPresentationUniqueKey = null;
             query.SeriesPresentationUniqueKey = seriesKey;
             query.IncludeItemTypes = new[] { typeof(Season).Name };
-            query.SortBy = new[] {ItemSortBy.SortName};
+            query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray();
 
             if (!config.DisplayMissingEpisodes)
             {
@@ -275,9 +275,9 @@ namespace MediaBrowser.Controller.Entities.TV
 
                 query.AncestorWithPresentationUniqueKey = null;
                 query.SeriesPresentationUniqueKey = seriesKey;
-                if (query.SortBy.Length == 0)
+                if (query.OrderBy.Length == 0)
                 {
-                    query.SortBy = new[] { ItemSortBy.SortName };
+                    query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray();
                 }
                 if (query.IncludeItemTypes.Length == 0)
                 {
@@ -301,7 +301,7 @@ namespace MediaBrowser.Controller.Entities.TV
                 AncestorWithPresentationUniqueKey = null,
                 SeriesPresentationUniqueKey = seriesKey,
                 IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
-                SortBy = new[] { ItemSortBy.SortName },
+                OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
                 DtoOptions = options
             };
             var config = user.Configuration;
@@ -410,7 +410,7 @@ namespace MediaBrowser.Controller.Entities.TV
                 AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
                 SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
                 IncludeItemTypes = new[] { typeof(Episode).Name },
-                SortBy = new[] { ItemSortBy.SortName },
+                OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
                 DtoOptions = options
             };
             if (user != null)
@@ -453,7 +453,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
             return episodes.Where(episode =>
             {
-                var episodeItem = (Episode) episode;
+                var episodeItem = (Episode)episode;
 
                 var currentSeasonNumber = supportSpecialsInSeason ? episodeItem.AiredSeasonNumber : episode.ParentIndexNumber;
                 if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value)

+ 7 - 11
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -397,7 +397,7 @@ namespace MediaBrowser.Controller.Entities
 
             }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null);
 
-            query.SortBy = new string[] { };
+            query.OrderBy = new Tuple<string, SortOrder>[] { };
 
             return PostFilterAndSort(items, parent, null, query, false, true);
         }
@@ -507,8 +507,7 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetMovieLatest(Folder parent, User user, InternalItemsQuery query)
         {
-            query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
-            query.SortOrder = SortOrder.Descending;
+            query.OrderBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray();
 
             query.Recursive = true;
             query.Parent = parent;
@@ -521,8 +520,7 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, InternalItemsQuery query)
         {
-            query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
-            query.SortOrder = SortOrder.Descending;
+            query.OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray();
             query.IsResumable = true;
             query.Recursive = true;
             query.Parent = parent;
@@ -633,8 +631,7 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, InternalItemsQuery query)
         {
-            query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
-            query.SortOrder = SortOrder.Descending;
+            query.OrderBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray();
 
             query.Recursive = true;
             query.Parent = parent;
@@ -663,8 +660,7 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetTvResume(Folder parent, User user, InternalItemsQuery query)
         {
-            query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
-            query.SortOrder = SortOrder.Descending;
+            query.OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray();
             query.IsResumable = true;
             query.Recursive = true;
             query.Parent = parent;
@@ -1104,9 +1100,9 @@ namespace MediaBrowser.Controller.Entities
         {
             items = items.DistinctBy(i => i.GetPresentationUniqueKey(), StringComparer.OrdinalIgnoreCase);
 
-            if (query.SortBy.Length > 0)
+            if (query.OrderBy.Length > 0)
             {
-                items = libraryManager.Sort(items, query.User, query.SortBy, query.SortOrder);
+                items = libraryManager.Sort(items, query.User, query.OrderBy);
             }
 
             var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();

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

@@ -181,8 +181,8 @@ namespace MediaBrowser.Controller.Library
         /// <param name="sortBy">The sort by.</param>
         /// <param name="sortOrder">The sort order.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy,
-                                   SortOrder sortOrder);
+        IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder);
+        IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<Tuple<string, SortOrder>> orderBy);
 
         /// <summary>
         /// Gets the user root folder.

+ 0 - 2
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -183,14 +183,12 @@
     <Compile Include="Net\IHttpResultFactory.cs" />
     <Compile Include="Net\IHttpServer.cs" />
     <Compile Include="Net\IServerManager.cs" />
-    <Compile Include="Net\IServiceRequest.cs" />
     <Compile Include="Net\ISessionContext.cs" />
     <Compile Include="Net\IWebSocket.cs" />
     <Compile Include="Net\IWebSocketConnection.cs" />
     <Compile Include="Net\IWebSocketListener.cs" />
     <Compile Include="Net\LoggedAttribute.cs" />
     <Compile Include="Net\SecurityException.cs" />
-    <Compile Include="Net\ServiceRequest.cs" />
     <Compile Include="Net\StaticResultOptions.cs" />
     <Compile Include="Net\WebSocketConnectEventArgs.cs" />
     <Compile Include="Net\WebSocketMessageInfo.cs" />

+ 4 - 3
MediaBrowser.Controller/Net/AuthenticatedAttribute.cs

@@ -25,6 +25,8 @@ namespace MediaBrowser.Controller.Net
         /// <value><c>true</c> if [allow before startup wizard]; otherwise, <c>false</c>.</value>
         public bool AllowBeforeStartupWizard { get; set; }
 
+        public bool AllowLocal { get; set; }
+
         /// <summary>
         /// The request filter is executed before the service.
         /// </summary>
@@ -33,9 +35,7 @@ namespace MediaBrowser.Controller.Net
         /// <param name="requestDto">The request DTO</param>
         public void RequestFilter(IRequest request, IResponse response, object requestDto)
         {
-            var serviceRequest = new ServiceRequest(request);
-
-            AuthService.Authenticate(serviceRequest, this);
+            AuthService.Authenticate(request, this);
         }
 
         /// <summary>
@@ -59,6 +59,7 @@ namespace MediaBrowser.Controller.Net
     {
         bool EscapeParentalControl { get; }
         bool AllowBeforeStartupWizard { get; }
+        bool AllowLocal { get; }
 
         string[] GetRoles();
     }

+ 3 - 3
MediaBrowser.Controller/Net/IAuthService.cs

@@ -1,9 +1,9 @@
-
+using MediaBrowser.Model.Services;
+
 namespace MediaBrowser.Controller.Net
 {
     public interface IAuthService
     {
-        void Authenticate(IServiceRequest request,
-            IAuthenticationAttributes authAttribtues);
+        void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues);
     }
 }

+ 3 - 2
MediaBrowser.Controller/Net/IAuthorizationContext.cs

@@ -1,4 +1,5 @@
-
+using MediaBrowser.Model.Services;
+
 namespace MediaBrowser.Controller.Net
 {
     public interface IAuthorizationContext
@@ -15,6 +16,6 @@ namespace MediaBrowser.Controller.Net
         /// </summary>
         /// <param name="requestContext">The request context.</param>
         /// <returns>AuthorizationInfo.</returns>
-        AuthorizationInfo GetAuthorizationInfo(IServiceRequest requestContext);
+        AuthorizationInfo GetAuthorizationInfo(IRequest requestContext);
     }
 }

+ 0 - 14
MediaBrowser.Controller/Net/IServiceRequest.cs

@@ -1,14 +0,0 @@
-using System.Collections.Generic;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Controller.Net
-{
-    public interface IServiceRequest
-    {
-        string RemoteIp { get; }
-        QueryParamCollection Headers { get; }
-        QueryParamCollection QueryString { get; }
-        IDictionary<string,object> Items { get; }
-        void AddResponseHeader(string name, string value);
-    }
-}

+ 3 - 2
MediaBrowser.Controller/Net/ISessionContext.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Session;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Services;
 
 namespace MediaBrowser.Controller.Net
 {
@@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Net
         Task<SessionInfo> GetSession(object requestContext);
         Task<User> GetUser(object requestContext);
 
-        Task<SessionInfo> GetSession(IServiceRequest requestContext);
-        Task<User> GetUser(IServiceRequest requestContext);
+        Task<SessionInfo> GetSession(IRequest requestContext);
+        Task<User> GetUser(IRequest requestContext);
     }
 }

+ 1 - 3
MediaBrowser.Controller/Net/LoggedAttribute.cs

@@ -30,10 +30,8 @@ namespace MediaBrowser.Controller.Net
         /// <param name="requestDto">The request DTO</param>
         public void Filter(IRequest request, IResponse response, object requestDto)
         {
-            var serviceRequest = new ServiceRequest(request);
-            
             //This code is executed before the service
-            var auth = AuthorizationContext.GetAuthorizationInfo(serviceRequest);
+            var auth = AuthorizationContext.GetAuthorizationInfo(request);
 
             if (auth != null)
             {

+ 0 - 42
MediaBrowser.Controller/Net/ServiceRequest.cs

@@ -1,42 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Controller.Net
-{
-    public class ServiceRequest : IServiceRequest
-    {
-        private readonly IRequest _request;
-
-        public ServiceRequest(IRequest request)
-        {
-            _request = request;
-        }
-
-        public string RemoteIp
-        {
-            get { return _request.RemoteIp; }
-        }
-
-        public QueryParamCollection Headers
-        {
-            get { return _request.Headers; }
-        }
-
-        public QueryParamCollection QueryString
-        {
-            get { return _request.QueryString; }
-        }
-
-        public IDictionary<string, object> Items
-        {
-            get { return _request.Items; }
-        }
-
-        public void AddResponseHeader(string name, string value)
-        {
-            _request.Response.AddHeader(name, value);
-        }
-    }
-}

+ 3 - 5
MediaBrowser.Controller/Playlists/Playlist.cs

@@ -149,8 +149,7 @@ namespace MediaBrowser.Controller.Playlists
                     Recursive = true,
                     IncludeItemTypes = new[] { typeof(Audio).Name },
                     GenreIds = new[] { musicGenre.Id.ToString("N") },
-                    SortBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName },
-                    SortOrder = SortOrder.Ascending,
+                    OrderBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
                     DtoOptions = options
                 });
             }
@@ -163,8 +162,7 @@ namespace MediaBrowser.Controller.Playlists
                     Recursive = true,
                     IncludeItemTypes = new[] { typeof(Audio).Name },
                     ArtistIds = new[] { musicArtist.Id.ToString("N") },
-                    SortBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName },
-                    SortOrder = SortOrder.Ascending,
+                    OrderBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
                     DtoOptions = options
                 });
             }
@@ -176,7 +174,7 @@ namespace MediaBrowser.Controller.Playlists
                 {
                     Recursive = true,
                     IsFolder = false,
-                    SortBy = new[] { ItemSortBy.SortName },
+                    OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
                     MediaTypes = new[] { mediaType },
                     EnableTotalRecordCount = false,
                     DtoOptions = options

+ 5 - 4
MediaBrowser.Model/Channels/ChannelItemQuery.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Model.Channels
@@ -35,16 +37,15 @@ namespace MediaBrowser.Model.Channels
         /// <value>The limit.</value>
         public int? Limit { get; set; }
 
-        public SortOrder? SortOrder { get; set; }
-        public string[] SortBy { get; set; }
         public ItemFilter[] Filters { get; set; }
         public ItemFields[] Fields { get; set; }
+        public Tuple<string, SortOrder>[] OrderBy { get; set; }
 
         public ChannelItemQuery()
         {
             Filters = new ItemFilter[] { };
-            SortBy = new string[] { };
             Fields = new ItemFields[] { };
+            OrderBy = new Tuple<string, SortOrder>[] { };
         }
     }
 

+ 2 - 12
MediaBrowser.Model/LiveTv/ProgramQuery.cs

@@ -12,7 +12,7 @@ namespace MediaBrowser.Model.LiveTv
         public ProgramQuery()
         {
             ChannelIds = new string[] { };
-            SortBy = new string[] { };
+            OrderBy = new Tuple<string, SortOrder>[] { };
             Genres = new string[] { };
             EnableTotalRecordCount = true;
             EnableUserData = true;
@@ -104,17 +104,7 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         public int? Limit { get; set; }
 
-        /// <summary>
-        /// What to sort the results by
-        /// </summary>
-        /// <value>The sort by.</value>
-        public string[] SortBy { get; set; }
-
-        /// <summary>
-        /// The sort order to return results with
-        /// </summary>
-        /// <value>The sort order.</value>
-        public SortOrder? SortOrder { get; set; }
+        public Tuple<string, SortOrder>[] OrderBy { get; set; }
 
         /// <summary>
         /// Limit results to items containing specific genres

+ 2 - 2
MediaBrowser.Model/Text/ITextEncoding.cs

@@ -7,8 +7,8 @@ namespace MediaBrowser.Model.Text
     {
         Encoding GetASCIIEncoding();
 
-        string GetDetectedEncodingName(byte[] bytes, string language, bool enableLanguageDetection);
-        Encoding GetDetectedEncoding(byte[] bytes, string language, bool enableLanguageDetection);
+        string GetDetectedEncodingName(byte[] bytes, int size, string language, bool enableLanguageDetection);
+        Encoding GetDetectedEncoding(byte[] bytes, int size, string language, bool enableLanguageDetection);
         Encoding GetEncodingFromCharset(string charset);
     }
 }

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.747</version>
+        <version>3.0.748</version>
         <title>Emby.Common</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.747</version>
+        <version>3.0.748</version>
         <title>Emby.Server.Core</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Emby Server.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.747" />
+            <dependency id="MediaBrowser.Common" version="3.0.748" />
         </dependencies>
     </metadata>
     <files>

+ 1 - 1
SharedVersion.cs

@@ -1,3 +1,3 @@
 using System.Reflection;
 
-[assembly: AssemblyVersion("3.2.30.6")]
+[assembly: AssemblyVersion("3.2.30.7")]

+ 56 - 0
SocketHttpListener/Net/HttpResponseStream.Managed.cs

@@ -289,9 +289,65 @@ namespace SocketHttpListener.Net
 
         public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
         {
+            //if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !_response.SendChunked && _response.ContentLength64 > 8192)
+            //{
+            //    if (EnableSendFileWithSocket)
+            //    {
+            //        return TransmitFileOverSocket(path, offset, count, fileShareMode, cancellationToken);
+            //    }
+            //}
+
             return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
         }
 
+        private readonly byte[] _emptyBuffer = new byte[] { };
+        private Task TransmitFileOverSocket(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
+        {
+            var ms = GetHeaders(false);
+
+            byte[] preBuffer;
+            if (ms != null)
+            {
+                using (var msCopy = new MemoryStream())
+                {
+                    ms.CopyTo(msCopy);
+                    preBuffer = msCopy.ToArray();
+                }
+            }
+            else
+            {
+                return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
+            }
+
+            //_logger.Info("Socket sending file {0} {1}", path, response.ContentLength64);
+
+            var taskCompletion = new TaskCompletionSource<bool>();
+
+            Action<IAsyncResult> callback = callbackResult =>
+            {
+                try
+                {
+                    _socket.EndSendFile(callbackResult);
+                    taskCompletion.TrySetResult(true);
+                }
+                catch (Exception ex)
+                {
+                    taskCompletion.TrySetException(ex);
+                }
+            };
+
+            var result = _socket.BeginSendFile(path, preBuffer, _emptyBuffer, TransmitFileOptions.UseDefaultWorkerThread, new AsyncCallback(callback), null);
+
+            if (result.CompletedSynchronously)
+            {
+                callback(result);
+            }
+
+            cancellationToken.Register(() => taskCompletion.TrySetCanceled());
+
+            return taskCompletion.Task;
+        }
+
         const int StreamCopyToBufferSize = 81920;
         private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
         {