ソースを参照

Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser

Eric Reed 12 年 前
コミット
6470c62c50
74 ファイル変更1343 行追加1124 行削除
  1. 0 59
      MediaBrowser.Api/Javascript/JavascriptApiClientService.cs
  2. 1 0
      MediaBrowser.Api/Library/LibraryService.cs
  3. 1 2
      MediaBrowser.Api/MediaBrowser.Api.csproj
  4. 3 5
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  5. 2 2
      MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
  6. 1 1
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  7. 1 1
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  8. 1 5
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  9. 18 3
      MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
  10. 1 0
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  11. 7 50
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  12. 58 0
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  13. 11 328
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  14. 1 0
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  15. 61 4
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  16. 1 1
      MediaBrowser.Controller/Drawing/ImageManager.cs
  17. 3 70
      MediaBrowser.Controller/Entities/Folder.cs
  18. 2 11
      MediaBrowser.Controller/Library/DtoBuilder.cs
  19. 14 1
      MediaBrowser.Controller/Library/ILibraryManager.cs
  20. 3 3
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  21. 0 1
      MediaBrowser.Controller/MediaInfo/ffmpeg20130209.zip.REMOVED.git-id
  22. 1 0
      MediaBrowser.Controller/MediaInfo/ffmpeg20130310.zip.REMOVED.git-id
  23. 0 248
      MediaBrowser.Controller/Sorting/BaseItemComparer.cs
  24. 17 0
      MediaBrowser.Controller/Sorting/IBaseItemComparer.cs
  25. 16 0
      MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs
  26. 0 33
      MediaBrowser.Controller/Sorting/SortOrder.cs
  27. 1 7
      MediaBrowser.Installer/App.xaml.cs
  28. 0 4
      MediaBrowser.Installer/MediaBrowser.Installer.csproj
  29. 34 48
      MediaBrowser.Model/DTO/BaseItemDto.cs
  30. 0 42
      MediaBrowser.Model/DTO/ItemSortBy.cs
  31. 0 22
      MediaBrowser.Model/DTO/MediaType.cs
  32. 1 1
      MediaBrowser.Model/Entities/ImageType.cs
  33. 6 7
      MediaBrowser.Model/MediaBrowser.Model.csproj
  34. 54 0
      MediaBrowser.Model/PropertyChanged.xml
  35. 1 6
      MediaBrowser.Model/Querying/ItemFields.cs
  36. 1 1
      MediaBrowser.Model/Querying/ItemFilter.cs
  37. 4 9
      MediaBrowser.Model/Querying/ItemQuery.cs
  38. 54 0
      MediaBrowser.Model/Querying/ItemSortBy.cs
  39. 4 4
      MediaBrowser.Model/Querying/ItemsByNameQuery.cs
  40. 3 2
      MediaBrowser.Model/Querying/ItemsResult.cs
  41. 12 2
      MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs
  42. 177 0
      MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
  43. 19 4
      MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
  44. 68 1
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  45. 12 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  46. 46 0
      MediaBrowser.Server.Implementations/Sorting/AlbumArtistComparer.cs
  47. 46 0
      MediaBrowser.Server.Implementations/Sorting/AlbumComparer.cs
  48. 46 0
      MediaBrowser.Server.Implementations/Sorting/ArtistComparer.cs
  49. 29 0
      MediaBrowser.Server.Implementations/Sorting/CommunityRatingComparer.cs
  50. 33 0
      MediaBrowser.Server.Implementations/Sorting/DateCreatedComparer.cs
  51. 56 0
      MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
  52. 52 0
      MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs
  53. 52 0
      MediaBrowser.Server.Implementations/Sorting/ProductionYearComparer.cs
  54. 33 0
      MediaBrowser.Server.Implementations/Sorting/RandomComparer.cs
  55. 32 0
      MediaBrowser.Server.Implementations/Sorting/RuntimeComparer.cs
  56. 33 0
      MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs
  57. 4 1
      MediaBrowser.Server.Implementations/Udp/UdpServer.cs
  58. 29 22
      MediaBrowser.ServerApplication/ApplicationHost.cs
  59. 53 10
      MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
  60. 4 1
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  61. BIN
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication_TemporaryKey.pfx
  62. 15 8
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  63. 0 0
      MediaBrowser.WebDashboard/ApiClient.js
  64. 2 5
      MediaBrowser.WebDashboard/Html/advancedMetadata.html
  65. 7 10
      MediaBrowser.WebDashboard/Html/metadata.html
  66. 67 70
      MediaBrowser.WebDashboard/Html/metadataImages.html
  67. 8 2
      MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js
  68. 7 1
      MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js
  69. 7 1
      MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js
  70. 1 0
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  71. 1 0
      MediaBrowser.WebDashboard/packages.config
  72. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  73. 1 1
      Nuget/MediaBrowser.Common.nuspec
  74. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 0 - 59
MediaBrowser.Api/Javascript/JavascriptApiClientService.cs

@@ -1,59 +0,0 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Server.Implementations.HttpServer;
-using ServiceStack.ServiceHost;
-using System;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Api.Javascript
-{
-    /// <summary>
-    /// Class GetJavascriptApiClient
-    /// </summary>
-    [Route("/JsApiClient.js", "GET")]
-    [ServiceStack.ServiceHost.Api(("Gets an api wrapper written in Javascript"))]
-    public class GetJavascriptApiClient
-    {
-        /// <summary>
-        /// Version identifier for caching
-        /// </summary>
-        /// <value>The v.</value>
-        public string V { get; set; }
-    }
-
-    /// <summary>
-    /// Class JavascriptApiClientService
-    /// </summary>
-    public class JavascriptApiClientService : BaseRestService
-    {
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public object Get(GetJavascriptApiClient request)
-        {
-            TimeSpan? cacheDuration = null;
-
-            // If there's a version number in the query string we can cache this unconditionally
-            if (!string.IsNullOrEmpty(request.V))
-            {
-                cacheDuration = TimeSpan.FromDays(365);
-            }
-
-            var assembly = GetType().Assembly.GetName();
-
-            return ToStaticResult(assembly.Version.ToString().GetMD5(), null, cacheDuration, MimeTypes.GetMimeType("script.js"), GetStream);
-        }
-
-        /// <summary>
-        /// Gets the stream.
-        /// </summary>
-        /// <returns>Stream.</returns>
-        private Task<Stream> GetStream()
-        {
-            return Task.FromResult(GetType().Assembly.GetManifestResourceStream("MediaBrowser.Api.Javascript.ApiClient.js"));
-        }
-    }
-}

+ 1 - 0
MediaBrowser.Api/Library/LibraryService.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Querying;
 using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using System;

+ 1 - 2
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -85,7 +85,6 @@
     <Compile Include="Images\ImageRequest.cs" />
     <Compile Include="Images\ImageService.cs" />
     <Compile Include="Images\ImageWriter.cs" />
-    <Compile Include="Javascript\JavascriptApiClientService.cs" />
     <Compile Include="Library\LibraryHelpers.cs" />
     <Compile Include="Library\LibraryService.cs" />
     <Compile Include="Library\LibraryStructureService.cs" />
@@ -107,6 +106,7 @@
     <Compile Include="ApiEntryPoint.cs" />
     <Compile Include="SystemService.cs" />
     <Compile Include="UserLibrary\BaseItemsByNameService.cs" />
+    <Compile Include="UserLibrary\BaseItemsRequest.cs" />
     <Compile Include="UserLibrary\GenresService.cs" />
     <Compile Include="UserLibrary\ItemsService.cs" />
     <Compile Include="UserLibrary\PersonsService.cs" />
@@ -141,7 +141,6 @@
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="Javascript\ApiClient.js" />
     <Content Include="options.xml" />
   </ItemGroup>
   <ItemGroup />

+ 3 - 5
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Server.Implementations.HttpServer;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -14,7 +15,6 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Server.Implementations.HttpServer;
 
 namespace MediaBrowser.Api.Playback
 {
@@ -650,10 +650,8 @@ namespace MediaBrowser.Api.Playback
                 state.VideoStream = GetMediaStream(media.MediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video, true);
                 state.SubtitleStream = GetMediaStream(media.MediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
             }
-            else
-            {
-                state.AudioStream = GetMediaStream(media.MediaStreams, null, MediaStreamType.Audio, true);
-            }
+
+            state.AudioStream = GetMediaStream(media.MediaStreams, null, MediaStreamType.Audio, true);
             
             return state;
         }

+ 2 - 2
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -18,8 +18,8 @@ namespace MediaBrowser.Api.Playback.Hls
 
     }
 
-    [Route("/Audio/{Id}/segments/{SegmentId}.mp3", "GET")]
-    [Route("/Audio/{Id}/segments/{SegmentId}.aac", "GET")]
+    [Route("/Audio/{Id}/segments/{SegmentId}/stream.mp3", "GET")]
+    [Route("/Audio/{Id}/segments/{SegmentId}/stream.aac", "GET")]
     [ServiceStack.ServiceHost.Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
     public class GetHlsAudioSegment
     {

+ 1 - 1
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -127,7 +127,7 @@ namespace MediaBrowser.Api.Playback.Hls
             // The segement paths within the playlist are phsyical, so strip that out to make it relative
             fileText = fileText.Replace(Path.GetDirectoryName(playlist) + Path.DirectorySeparatorChar, string.Empty);
 
-            fileText = fileText.Replace(SegmentFilePrefix, "segments/");
+            fileText = fileText.Replace(SegmentFilePrefix, "segments/").Replace(".ts", "/stream.ts").Replace(".aac", "/stream.aac").Replace(".mp3", "/stream.mp3");
 
             // Even though we specify target duration of 9, ffmpeg seems unable to keep all segments under that amount
             fileText = fileText.Replace("#EXT-X-TARGETDURATION:9", "#EXT-X-TARGETDURATION:10");

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

@@ -14,7 +14,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
     }
 
-    [Route("/Videos/{Id}/segments/{SegmentId}.ts", "GET")]
+    [Route("/Videos/{Id}/segments/{SegmentId}/stream.ts", "GET")]
     [ServiceStack.ServiceHost.Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
     public class GetHlsVideoSegment
     {

+ 1 - 5
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -135,11 +135,7 @@ namespace MediaBrowser.Api.Playback.Progressive
                 ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
             }
 
-            return new ProgressiveStreamWriter
-            {
-                Path = outputPath,
-                State = state
-            };
+            return new ProgressiveStreamWriter(outputPath, state, Logger);
         }
 
         /// <summary>

+ 18 - 3
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -9,9 +9,22 @@ namespace MediaBrowser.Api.Playback.Progressive
 {
     public class ProgressiveStreamWriter : IStreamWriter
     {
-        public string Path { get; set; }
-        public StreamState State { get; set; }
-        public ILogger Logger { get; set; }
+        private string Path { get; set; }
+        private StreamState State { get; set; }
+        private ILogger Logger { get; set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ProgressiveStreamWriter" /> class.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="state">The state.</param>
+        /// <param name="logger">The logger.</param>
+        public ProgressiveStreamWriter(string path, StreamState state, ILogger logger)
+        {
+            Path = path;
+            State = state;
+            Logger = logger;
+        }
 
         /// <summary>
         /// Writes to.
@@ -38,6 +51,8 @@ namespace MediaBrowser.Api.Playback.Progressive
             catch (Exception ex)
             {
                 Logger.ErrorException("Error streaming media", ex);
+
+                throw;
             }
             finally
             {

+ 1 - 0
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -20,6 +20,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     [Route("/Videos/{Id}/stream.mkv", "GET")]
     [Route("/Videos/{Id}/stream.mpeg", "GET")]
     [Route("/Videos/{Id}/stream.avi", "GET")]
+    [Route("/Videos/{Id}/stream.m2ts", "GET")]
     [Route("/Videos/{Id}/stream", "GET")]
     [ServiceStack.ServiceHost.Api(Description = "Gets a video stream")]
     public class GetVideoStream : VideoStreamRequest

+ 7 - 50
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -1,7 +1,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using System;
@@ -23,12 +23,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// The _user manager
         /// </summary>
         protected readonly IUserManager UserManager;
+        /// <summary>
+        /// The library manager
+        /// </summary>
         protected readonly ILibraryManager LibraryManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseItemsByNameService{TItemType}" /> class.
         /// </summary>
         /// <param name="userManager">The user manager.</param>
+        /// <param name="libraryManager">The library manager.</param>
         protected BaseItemsByNameService(IUserManager userManager, ILibraryManager libraryManager)
         {
             UserManager = userManager;
@@ -44,7 +48,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var user = UserManager.GetUserById(request.UserId);
 
-            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, UserManager, LibraryManager, user.Id);
+            var item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.ParentId, UserManager, LibraryManager, user.Id);
 
             IEnumerable<BaseItem> items;
 
@@ -157,54 +161,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// <summary>
     /// Class GetItemsByName
     /// </summary>
-    public class GetItemsByName : IReturn<ItemsResult>
+    public class GetItemsByName : BaseItemsRequest, IReturn<ItemsResult>
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public Guid UserId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the start index.
-        /// </summary>
-        /// <value>The start index.</value>
-        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? StartIndex { get; set; }
-
-        /// <summary>
-        /// Gets or sets the size of the page.
-        /// </summary>
-        /// <value>The size of the page.</value>
-        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? Limit { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this <see cref="GetItemsByName" /> is recursive.
-        /// </summary>
-        /// <value><c>true</c> if recursive; otherwise, <c>false</c>.</value>
-        [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
-        public bool Recursive { get; set; }
-
-        /// <summary>
-        /// Gets or sets the sort order.
-        /// </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; }
-
-        /// <summary>
-        /// If specified the search will be localized within a specific item or folder
-        /// </summary>
-        /// <value>The item id.</value>
-        [ApiMember(Name = "Id", Description = "If specified the search will be localized within a specific item or folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string Id { get; set; }
-
-        /// <summary>
-        /// Fields to return within the items, in addition to basic information
-        /// </summary>
-        /// <value>The fields.</value>
-        public string Fields { get; set; }
     }
 }

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

@@ -0,0 +1,58 @@
+using MediaBrowser.Model.Entities;
+using ServiceStack.ServiceHost;
+using System;
+
+namespace MediaBrowser.Api.UserLibrary
+{
+    public abstract class BaseItemsRequest
+    {
+        /// <summary>
+        /// Gets or sets the user id.
+        /// </summary>
+        /// <value>The user id.</value>
+        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public Guid UserId { get; set; }
+
+        /// <summary>
+        /// Skips over a given number of items within the results. Use for paging.
+        /// </summary>
+        /// <value>The start index.</value>
+        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? StartIndex { get; set; }
+
+        /// <summary>
+        /// The maximum number of items to return
+        /// </summary>
+        /// <value>The limit.</value>
+        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? Limit { get; set; }
+
+        /// <summary>
+        /// Whether or not to perform the query recursively
+        /// </summary>
+        /// <value><c>true</c> if recursive; otherwise, <c>false</c>.</value>
+        [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+        public bool Recursive { get; set; }
+
+        /// <summary>
+        /// Gets or sets the sort order.
+        /// </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; }
+
+        /// <summary>
+        /// Specify this to localize the search to a specific item or folder. Omit to use the root.
+        /// </summary>
+        /// <value>The parent id.</value>
+        [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ParentId { get; set; }
+
+        /// <summary>
+        /// Fields to return within the items, in addition to basic information
+        /// </summary>
+        /// <value>The fields.</value>
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: AudioInfo, Chapters, DateCreated, DisplayMediaType, DisplayPreferences, Genres, ItemCounts, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SeriesInfo, SortName, Studios, Taglines, TrailerUrls, UserData", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Fields { get; set; }
+    }
+}

+ 11 - 328
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -1,8 +1,7 @@
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using System;
@@ -17,43 +16,8 @@ namespace MediaBrowser.Api.UserLibrary
     /// </summary>
     [Route("/Users/{UserId}/Items", "GET")]
     [ServiceStack.ServiceHost.Api(Description = "Gets items based on a query.")]
-    public class GetItems : IReturn<ItemsResult>
+    public class GetItems : BaseItemsRequest, IReturn<ItemsResult>
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public Guid UserId { get; set; }
-
-        /// <summary>
-        /// Specify this to localize the search to a specific item or folder. Omit to use the root.
-        /// </summary>
-        /// <value>The parent id.</value>
-        [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string ParentId { get; set; }
-
-        /// <summary>
-        /// Skips over a given number of items within the results. Use for paging.
-        /// </summary>
-        /// <value>The start index.</value>
-        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? StartIndex { get; set; }
-
-        /// <summary>
-        /// The maximum number of items to return
-        /// </summary>
-        /// <value>The limit.</value>
-        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? Limit { get; set; }
-
-        /// <summary>
-        /// Whether or not to perform the query recursively
-        /// </summary>
-        /// <value><c>true</c> if recursive; otherwise, <c>false</c>.</value>
-        [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
-        public bool Recursive { get; set; }
-
         /// <summary>
         /// Limit results to items containing a specific person
         /// </summary>
@@ -81,40 +45,20 @@ namespace MediaBrowser.Api.UserLibrary
         /// <value>The index by.</value>
         public string IndexBy { get; set; }
 
-        /// <summary>
-        /// The dynamic, localized sort function name
-        /// </summary>
-        /// <value>The dynamic sort by.</value>
-        public string DynamicSortBy { get; set; }
-
         /// <summary>
         /// What to sort the results by
         /// </summary>
         /// <value>The sort by.</value>
-        [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album,AlbumArtist,Artist,DateCreated,DatePlayed,PremiereDate,SortName,Random", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, CommunityRating, DateCreated, DatePlayed, PremiereDate, ProductionYear, SortName, Random, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string SortBy { get; set; }
 
-        /// <summary>
-        /// The sort order to return results with
-        /// </summary>
-        /// <value>The sort order.</value>
-        [ApiMember(Name = "SortOrder", Description = "Optional. Ascending / Descending sort order", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string SortOrder { get; set; }
-
         /// <summary>
         /// Filters to apply to the results
         /// </summary>
         /// <value>The filters.</value>
-        [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder,IsNotFolder,IsUnplayed,IsPlayed,IsFavorite,IsResumable", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsRecentlyAdded, IsResumable", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Filters { get; set; }
 
-        /// <summary>
-        /// Fields to return within the items, in addition to basic information
-        /// </summary>
-        /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: AudioInfo, Chapters, DateCreated, DisplayMediaType, DisplayPreferences, Genres, ItemCounts, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SeriesInfo, SortName, Studios, Taglines, TrailerUrls, UserData", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string Fields { get; set; }
-
         /// <summary>
         /// Limit results to items containing specific genres
         /// </summary>
@@ -254,7 +198,7 @@ namespace MediaBrowser.Api.UserLibrary
                 return ((Folder)item).GetRecursiveChildren(user);
             }
 
-            return ((Folder)item).GetChildren(user, request.IndexBy, request.DynamicSortBy, GetSortOrder(request));
+            return ((Folder)item).GetChildren(user, request.IndexBy);
         }
 
         /// <summary>
@@ -266,58 +210,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{BaseItem}.</returns>
         private IEnumerable<BaseItem> ApplySortOrder(GetItems request, IEnumerable<BaseItem> items, User user)
         {
-            var isFirst = true;
-            var descending = (GetSortOrder(request) ?? SortOrder.Ascending) == SortOrder.Descending;
-
-            IOrderedEnumerable<BaseItem> orderedItems = null;
+            var orderBy = GetOrderBy(request).ToArray();
 
-            foreach (var orderBy in GetOrderBy(request).Select(o => GetComparer(o, user)))
-            {
-                if (isFirst)
-                {
-                    orderedItems = descending ? items.OrderByDescending(i => i, orderBy) : items.OrderBy(i => i, orderBy);
-                }
-                else
-                {
-                    orderedItems = descending ? orderedItems.ThenByDescending(i => i, orderBy) : orderedItems.ThenBy(i => i, orderBy);
-                }
-
-                isFirst = false;
-            }
-
-            return orderedItems ?? items;
-        }
-
-        /// <summary>
-        /// Gets the comparer.
-        /// </summary>
-        /// <param name="sortBy">The sort by.</param>
-        /// <param name="user">The user.</param>
-        /// <returns>IComparer{BaseItem}.</returns>
-        /// <exception cref="System.ArgumentException"></exception>
-        private IComparer<BaseItem> GetComparer(ItemSortBy sortBy, User user)
-        {
-            switch (sortBy)
-            {
-                case ItemSortBy.Album:
-                    return new AlbumComparer();
-                case ItemSortBy.AlbumArtist:
-                    return new AlbumArtistComparer();
-                case ItemSortBy.Artist:
-                    return new ArtistComparer();
-                case ItemSortBy.Random:
-                    return new RandomComparer();
-                case ItemSortBy.DateCreated:
-                    return new DateCreatedComparer();
-                case ItemSortBy.SortName:
-                    return new SortNameComparer();
-                case ItemSortBy.PremiereDate:
-                    return new PremiereDateComparer();
-                case ItemSortBy.DatePlayed:
-                    return new DatePlayedComparer { User = user };
-                default:
-                    throw new ArgumentException();
-            }
+            return orderBy.Length == 0 ? items : _libraryManager.Sort(items, user, orderBy, request.SortOrder ?? SortOrder.Ascending);
         }
 
         /// <summary>
@@ -465,7 +360,7 @@ namespace MediaBrowser.Api.UserLibrary
                 return item.ScreenshotImagePaths != null && item.ScreenshotImagePaths.Count > 0;
             }
 
-            if (imageType == ImageType.ChapterImage)
+            if (imageType == ImageType.Chapter)
             {
                 var video = item as Video;
 
@@ -521,21 +416,6 @@ namespace MediaBrowser.Api.UserLibrary
             return items;
         }
 
-        /// <summary>
-        /// Gets the sort order.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Nullable{SortOrder}.</returns>
-        private SortOrder? GetSortOrder(GetItems request)
-        {
-            if (string.IsNullOrEmpty(request.SortOrder))
-            {
-                return null;
-            }
-
-            return (SortOrder)Enum.Parse(typeof(SortOrder), request.SortOrder, true);
-        }
-
         /// <summary>
         /// Gets the filters.
         /// </summary>
@@ -575,16 +455,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>IEnumerable{ItemSortBy}.</returns>
-        private IEnumerable<ItemSortBy> GetOrderBy(GetItems request)
+        private IEnumerable<string> GetOrderBy(GetItems request)
         {
             var val = request.SortBy;
 
             if (string.IsNullOrEmpty(val))
             {
-                return new ItemSortBy[] { };
+                return new string[] { };
             }
 
-            return val.Split(',').Select(v => (ItemSortBy)Enum.Parse(typeof(ItemSortBy), v, true));
+            return val.Split(',');
         }
 
         /// <summary>
@@ -621,201 +501,4 @@ namespace MediaBrowser.Api.UserLibrary
             return x.DateCreated.CompareTo(y.DateCreated);
         }
     }
-
-    /// <summary>
-    /// Class RandomComparer
-    /// </summary>
-    public class RandomComparer : IComparer<BaseItem>
-    {
-        /// <summary>
-        /// Compares the specified x.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <param name="y">The y.</param>
-        /// <returns>System.Int32.</returns>
-        public int Compare(BaseItem x, BaseItem y)
-        {
-            return Guid.NewGuid().CompareTo(Guid.NewGuid());
-        }
-    }
-
-    /// <summary>
-    /// Class SortNameComparer
-    /// </summary>
-    public class SortNameComparer : IComparer<BaseItem>
-    {
-        /// <summary>
-        /// Compares the specified x.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <param name="y">The y.</param>
-        /// <returns>System.Int32.</returns>
-        public int Compare(BaseItem x, BaseItem y)
-        {
-            return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase);
-        }
-    }
-
-    /// <summary>
-    /// Class AlbumArtistComparer
-    /// </summary>
-    public class AlbumArtistComparer : IComparer<BaseItem>
-    {
-        /// <summary>
-        /// Compares the specified x.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <param name="y">The y.</param>
-        /// <returns>System.Int32.</returns>
-        public int Compare(BaseItem x, BaseItem y)
-        {
-            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
-        }
-
-        /// <summary>
-        /// Gets the value.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <returns>System.String.</returns>
-        private string GetValue(BaseItem x)
-        {
-            var audio = x as Audio;
-
-            return audio == null ? string.Empty : audio.AlbumArtist;
-        }
-    }
-
-    /// <summary>
-    /// Class AlbumComparer
-    /// </summary>
-    public class AlbumComparer : IComparer<BaseItem>
-    {
-        /// <summary>
-        /// Compares the specified x.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <param name="y">The y.</param>
-        /// <returns>System.Int32.</returns>
-        public int Compare(BaseItem x, BaseItem y)
-        {
-            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
-        }
-
-        /// <summary>
-        /// Gets the value.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <returns>System.String.</returns>
-        private string GetValue(BaseItem x)
-        {
-            var audio = x as Audio;
-
-            return audio == null ? string.Empty : audio.Album;
-        }
-    }
-
-    /// <summary>
-    /// Class ArtistComparer
-    /// </summary>
-    public class ArtistComparer : IComparer<BaseItem>
-    {
-        /// <summary>
-        /// Compares the specified x.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <param name="y">The y.</param>
-        /// <returns>System.Int32.</returns>
-        public int Compare(BaseItem x, BaseItem y)
-        {
-            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
-        }
-
-        /// <summary>
-        /// Gets the value.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <returns>System.String.</returns>
-        private string GetValue(BaseItem x)
-        {
-            var audio = x as Audio;
-
-            return audio == null ? string.Empty : audio.Artist;
-        }
-    }
-
-    /// <summary>
-    /// Class PremiereDateComparer
-    /// </summary>
-    public class PremiereDateComparer : IComparer<BaseItem>
-    {
-        /// <summary>
-        /// Compares the specified x.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <param name="y">The y.</param>
-        /// <returns>System.Int32.</returns>
-        public int Compare(BaseItem x, BaseItem y)
-        {
-            return GetDate(x).CompareTo(GetDate(y));
-        }
-
-        /// <summary>
-        /// Gets the date.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <returns>DateTime.</returns>
-        private DateTime GetDate(BaseItem x)
-        {
-            if (x.PremiereDate.HasValue)
-            {
-                return x.PremiereDate.Value;
-            }
-
-            if (x.ProductionYear.HasValue)
-            {
-                return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc);
-            }
-            return DateTime.MaxValue;
-        }
-    }
-
-    /// <summary>
-    /// Class DatePlayedComparer
-    /// </summary>
-    public class DatePlayedComparer : IComparer<BaseItem>
-    {
-        /// <summary>
-        /// Gets or sets the user.
-        /// </summary>
-        /// <value>The user.</value>
-        public User User { get; set; }
-
-        /// <summary>
-        /// Compares the specified x.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <param name="y">The y.</param>
-        /// <returns>System.Int32.</returns>
-        public int Compare(BaseItem x, BaseItem y)
-        {
-            return GetDate(x).CompareTo(GetDate(y));
-        }
-
-        /// <summary>
-        /// Gets the date.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <returns>DateTime.</returns>
-        private DateTime GetDate(BaseItem x)
-        {
-            var userdata = x.GetUserData(User, false);
-
-            if (userdata != null && userdata.LastPlayedDate.HasValue)
-            {
-                return userdata.LastPlayedDate.Value;
-            }
-
-            return DateTime.MinValue;
-        }
-    }
 }

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

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Connectivity;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.Text.Controller;

+ 61 - 4
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -13,7 +13,6 @@ using MediaBrowser.Common.Security;
 using MediaBrowser.Common.Updates;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.System;
 using MediaBrowser.Model.Updates;
 using SimpleInjector;
 using System;
@@ -26,6 +25,10 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Common.Implementations
 {
+    /// <summary>
+    /// Class BaseApplicationHost
+    /// </summary>
+    /// <typeparam name="TApplicationPathsType">The type of the T application paths type.</typeparam>
     public abstract class BaseApplicationHost<TApplicationPathsType> : IApplicationHost
         where TApplicationPathsType : class, IApplicationPaths, new()
     {
@@ -87,6 +90,7 @@ namespace MediaBrowser.Common.Implementations
         /// <summary>
         /// Gets assemblies that failed to load
         /// </summary>
+        /// <value>The failed assemblies.</value>
         public List<string> FailedAssemblies { get; protected set; }
 
         /// <summary>
@@ -148,11 +152,31 @@ namespace MediaBrowser.Common.Implementations
         /// </summary>
         /// <value>The kernel.</value>
         protected ITaskManager TaskManager { get; private set; }
+        /// <summary>
+        /// Gets the security manager.
+        /// </summary>
+        /// <value>The security manager.</value>
         protected ISecurityManager SecurityManager { get; private set; }
+        /// <summary>
+        /// Gets the package manager.
+        /// </summary>
+        /// <value>The package manager.</value>
         protected IPackageManager PackageManager { get; private set; }
+        /// <summary>
+        /// Gets the HTTP client.
+        /// </summary>
+        /// <value>The HTTP client.</value>
         protected IHttpClient HttpClient { get; private set; }
+        /// <summary>
+        /// Gets the network manager.
+        /// </summary>
+        /// <value>The network manager.</value>
         protected INetworkManager NetworkManager { get; private set; }
 
+        /// <summary>
+        /// Gets the configuration manager.
+        /// </summary>
+        /// <value>The configuration manager.</value>
         protected IConfigurationManager ConfigurationManager { get; private set; }
 
         /// <summary>
@@ -187,7 +211,21 @@ namespace MediaBrowser.Common.Implementations
 
             FindParts();
 
-            Task.Run(() => ConfigureAutoRunAtStartup());
+            await RunStartupTasks().ConfigureAwait(false);
+        }
+
+        /// <summary>
+        /// Runs the startup tasks.
+        /// </summary>
+        /// <returns>Task.</returns>
+        protected virtual Task RunStartupTasks()
+        {
+            return Task.Run(() =>
+            {
+                Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
+
+                Task.Run(() => ConfigureAutoRunAtStartup());
+            });
         }
 
         /// <summary>
@@ -202,6 +240,10 @@ namespace MediaBrowser.Common.Implementations
         /// <value>The name of the log file prefix.</value>
         protected abstract string LogFilePrefixName { get; }
 
+        /// <summary>
+        /// Gets the configuration manager.
+        /// </summary>
+        /// <returns>IConfigurationManager.</returns>
         protected abstract IConfigurationManager GetConfigurationManager();
 
         /// <summary>
@@ -210,8 +252,6 @@ namespace MediaBrowser.Common.Implementations
         protected virtual void FindParts()
         {
             Plugins = GetExports<IPlugin>();
-            
-            Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
         }
 
         /// <summary>
@@ -236,6 +276,7 @@ namespace MediaBrowser.Common.Implementations
         /// <summary>
         /// Registers resources that classes will depend on
         /// </summary>
+        /// <returns>Task.</returns>
         protected virtual Task RegisterResources()
         {
             return Task.Run(() =>
@@ -509,10 +550,23 @@ namespace MediaBrowser.Common.Implementations
             }
         }
 
+        /// <summary>
+        /// Restarts this instance.
+        /// </summary>
         public abstract void Restart();
 
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance can self update.
+        /// </summary>
+        /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
         public abstract bool CanSelfUpdate { get; }
 
+        /// <summary>
+        /// Checks for update.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <param name="progress">The progress.</param>
+        /// <returns>Task{CheckForUpdateResult}.</returns>
         public abstract Task<CheckForUpdateResult> CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress<double> progress);
 
         /// <summary>
@@ -529,6 +583,9 @@ namespace MediaBrowser.Common.Implementations
             EventHelper.QueueEventIfNotNull(ApplicationUpdated, this, new GenericEventArgs<Version> { Argument = package.version }, Logger);
         }
 
+        /// <summary>
+        /// Shuts down.
+        /// </summary>
         public abstract void Shutdown();
     }
 }

+ 1 - 1
MediaBrowser.Controller/Drawing/ImageManager.cs

@@ -356,7 +356,7 @@ namespace MediaBrowser.Controller.Drawing
                 return item.ScreenshotImagePaths[imageIndex];
             }
 
-            if (imageType == ImageType.ChapterImage)
+            if (imageType == ImageType.Chapter)
             {
                 var video = (Video)item;
 

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

@@ -13,7 +13,6 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
-using SortOrder = MediaBrowser.Controller.Sorting.SortOrder;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -177,63 +176,6 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        #region Sorting
-
-        /// <summary>
-        /// The _sort by options
-        /// </summary>
-        private Dictionary<string, IComparer<BaseItem>> _sortByOptions;
-        /// <summary>
-        /// Dictionary of sort options - consists of a display value (localized) and an IComparer of BaseItem
-        /// </summary>
-        /// <value>The sort by options.</value>
-        [IgnoreDataMember]
-        public Dictionary<string, IComparer<BaseItem>> SortByOptions
-        {
-            get { return _sortByOptions ?? (_sortByOptions = GetSortByOptions()); }
-        }
-
-        /// <summary>
-        /// Returns the valid set of sort by options for this folder type.
-        /// Override or extend to modify.
-        /// </summary>
-        /// <returns>Dictionary{System.StringIComparer{BaseItem}}.</returns>
-        protected virtual Dictionary<string, IComparer<BaseItem>> GetSortByOptions()
-        {
-            return new Dictionary<string, IComparer<BaseItem>> {            
-                {LocalizedStrings.Instance.GetString("NameDispPref"), new BaseItemComparer(SortOrder.Name, Logger)},
-                {LocalizedStrings.Instance.GetString("DateDispPref"), new BaseItemComparer(SortOrder.Date, Logger)},
-                {LocalizedStrings.Instance.GetString("RatingDispPref"), new BaseItemComparer(SortOrder.Rating, Logger)},
-                {LocalizedStrings.Instance.GetString("RuntimeDispPref"), new BaseItemComparer(SortOrder.Runtime, Logger)},
-                {LocalizedStrings.Instance.GetString("YearDispPref"), new BaseItemComparer(SortOrder.Year, Logger)}
-            };
-
-        }
-
-        /// <summary>
-        /// Get a sorting comparer by name
-        /// </summary>
-        /// <param name="name">The name.</param>
-        /// <returns>IComparer{BaseItem}.</returns>
-        private IComparer<BaseItem> GetSortingFunction(string name)
-        {
-            IComparer<BaseItem> sorting;
-            SortByOptions.TryGetValue(name ?? "", out sorting);
-            return sorting ?? new BaseItemComparer(SortOrder.Name, Logger);
-        }
-
-        /// <summary>
-        /// Get the list of sort by choices for this folder (localized).
-        /// </summary>
-        /// <value>The sort by option strings.</value>
-        [IgnoreDataMember]
-        public IEnumerable<string> SortByOptionStrings
-        {
-            get { return SortByOptions.Keys; }
-        }
-
-        #endregion
-
         #region Indexing
 
         /// <summary>
@@ -877,11 +819,9 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <param name="user">The user.</param>
         /// <param name="indexBy">The index by.</param>
-        /// <param name="sortBy">The sort by.</param>
-        /// <param name="sortOrder">The sort order.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
-        public virtual IEnumerable<BaseItem> GetChildren(User user, string indexBy = null, string sortBy = null, Model.Entities.SortOrder? sortOrder = null)
+        public virtual IEnumerable<BaseItem> GetChildren(User user, string indexBy = null)
         {
             if (user == null)
             {
@@ -889,7 +829,7 @@ namespace MediaBrowser.Controller.Entities
             }
 
             //the true root should return our users root folder children
-            if (IsPhysicalRoot) return user.RootFolder.GetChildren(user, indexBy, sortBy, sortOrder);
+            if (IsPhysicalRoot) return user.RootFolder.GetChildren(user, indexBy);
 
             IEnumerable<BaseItem> result = null;
 
@@ -904,14 +844,7 @@ namespace MediaBrowser.Controller.Entities
                 result = ActualChildren.Where(c => c.IsVisible(user));
             }
 
-            if (string.IsNullOrEmpty(sortBy))
-            {
-                return result;
-            }
-
-            return sortOrder.HasValue && sortOrder.Value == Model.Entities.SortOrder.Descending
-                       ? result.OrderByDescending(i => i, GetSortingFunction(sortBy))
-                       : result.OrderBy(i => i, GetSortingFunction(sortBy));
+            return result;
         }
 
         /// <summary>

+ 2 - 11
MediaBrowser.Controller/Library/DtoBuilder.cs

@@ -11,6 +11,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Controller.Library
 {
@@ -160,8 +161,6 @@ namespace MediaBrowser.Controller.Library
         /// <param name="fields">The fields.</param>
         private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields)
         {
-            dto.IsNew = item.IsRecentlyAdded(user);
-
             if (fields.Contains(ItemFields.UserData))
             {
                 var userData = item.GetUserData(user, false);
@@ -366,18 +365,10 @@ namespace MediaBrowser.Controller.Library
             {
                 var folder = (Folder)item;
 
-                dto.IsRoot = folder.IsRoot;
-                dto.IsVirtualFolder = folder.IsVirtualFolder;
-
                 if (fields.Contains(ItemFields.IndexOptions))
                 {
                     dto.IndexOptions = folder.IndexByOptionStrings.ToArray();
                 }
-
-                if (fields.Contains(ItemFields.SortOptions))
-                {
-                    dto.SortOptions = folder.SortByOptionStrings.ToArray();
-                }
             }
 
             // Add audio info
@@ -678,7 +669,7 @@ namespace MediaBrowser.Controller.Library
 
             if (!string.IsNullOrEmpty(chapterInfo.ImagePath))
             {
-                dto.ImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.ChapterImage, chapterInfo.ImagePath);
+                dto.ImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Chapter, chapterInfo.ImagePath);
             }
 
             return dto;

+ 14 - 1
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
@@ -158,7 +159,19 @@ namespace MediaBrowser.Controller.Library
         /// <param name="pluginFolders">The plugin folders.</param>
         /// <param name="resolvers">The resolvers.</param>
         /// <param name="introProviders">The intro providers.</param>
+        /// <param name="itemComparers">The item comparers.</param>
         void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders,
-                      IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders);
+                      IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers);
+
+        /// <summary>
+        /// Sorts the specified items.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <param name="user">The user.</param>
+        /// <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);
     }
 }

+ 3 - 3
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -185,8 +185,8 @@
     <Compile Include="Providers\FolderProviderFromXml.cs" />
     <Compile Include="Providers\ImageFromMediaLocationProvider.cs" />
     <Compile Include="Providers\MediaInfo\FFProbeVideoInfoProvider.cs" />
-    <Compile Include="Sorting\BaseItemComparer.cs" />
-    <Compile Include="Sorting\SortOrder.cs" />
+    <Compile Include="Sorting\IBaseItemComparer.cs" />
+    <Compile Include="Sorting\IUserBaseItemComparer.cs" />
     <Compile Include="Updates\IInstallationManager.cs" />
     <Compile Include="Weather\IWeatherProvider.cs" />
     <Compile Include="Providers\BaseItemXmlParser.cs" />
@@ -202,7 +202,7 @@
     </ProjectReference>
   </ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="MediaInfo\ffmpeg20130209.zip" />
+    <EmbeddedResource Include="MediaInfo\ffmpeg20130310.zip" />
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>

+ 0 - 1
MediaBrowser.Controller/MediaInfo/ffmpeg20130209.zip.REMOVED.git-id

@@ -1 +0,0 @@
-985770c0d2633a13719be2e5cf19554262415f62

+ 1 - 0
MediaBrowser.Controller/MediaInfo/ffmpeg20130310.zip.REMOVED.git-id

@@ -0,0 +1 @@
+a005e50576665b191cbd02b42d6260bffb764690

+ 0 - 248
MediaBrowser.Controller/Sorting/BaseItemComparer.cs

@@ -1,248 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Collections.Generic;
-
-namespace MediaBrowser.Controller.Sorting {
-    /// <summary>
-    /// Class BaseItemComparer
-    /// </summary>
-    public class BaseItemComparer : IComparer<BaseItem> {
-        /// <summary>
-        /// The _order
-        /// </summary>
-        private readonly SortOrder _order;
-        /// <summary>
-        /// The _property name
-        /// </summary>
-        private readonly string _propertyName;
-        /// <summary>
-        /// The _compare culture
-        /// </summary>
-        private readonly StringComparison _compareCulture = StringComparison.CurrentCultureIgnoreCase;
-
-        /// <summary>
-        /// Gets or sets the logger.
-        /// </summary>
-        /// <value>The logger.</value>
-        private ILogger Logger { get; set; }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseItemComparer" /> class.
-        /// </summary>
-        /// <param name="order">The order.</param>
-        /// <param name="logger">The logger.</param>
-        public BaseItemComparer(SortOrder order, ILogger logger) {
-            _order = order;
-            Logger = logger;
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseItemComparer" /> class.
-        /// </summary>
-        /// <param name="order">The order.</param>
-        /// <param name="compare">The compare.</param>
-        /// <param name="logger">The logger.</param>
-        public BaseItemComparer(SortOrder order, StringComparison compare, ILogger logger)
-        {
-            _order = order;
-            _compareCulture = compare;
-            Logger = logger;
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseItemComparer" /> class.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <param name="logger">The logger.</param>
-        public BaseItemComparer(string property, ILogger logger)
-        {
-            _order = SortOrder.Custom;
-            _propertyName = property;
-            Logger = logger;
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseItemComparer" /> class.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <param name="compare">The compare.</param>
-        /// <param name="logger">The logger.</param>
-        public BaseItemComparer(string property, StringComparison compare, ILogger logger)
-        {
-            _order = SortOrder.Custom;
-            _propertyName = property;
-            _compareCulture = compare;
-            Logger = logger;
-        }
-
-        #region IComparer<BaseItem> Members
-
-        /// <summary>
-        /// Compares the specified x.
-        /// </summary>
-        /// <param name="x">The x.</param>
-        /// <param name="y">The y.</param>
-        /// <returns>System.Int32.</returns>
-        public int Compare(BaseItem x, BaseItem y) {
-            int compare = 0;
-
-            switch (_order) {
-
-                case SortOrder.Date:
-                    compare = -x.DateCreated.CompareTo(y.DateCreated);
-                    break;
-
-                case SortOrder.Year:
-
-                    var xProductionYear = x.ProductionYear ?? 0;
-                    var yProductionYear = y.ProductionYear ?? 0;
-
-
-                    compare = yProductionYear.CompareTo(xProductionYear); 
-                    break;
-
-                case SortOrder.Rating:
-
-                    var xRating = x.CommunityRating ?? 0;
-                    var yRating = y.CommunityRating ?? 0;
-
-                    compare = yRating.CompareTo(xRating);
-                    break;
-
-                case SortOrder.Runtime:
-                    var xRuntime = x.RunTimeTicks ?? 0;
-                    var yRuntime = y.RunTimeTicks ?? 0;
-
-                    compare = xRuntime.CompareTo(yRuntime);
-                    break;
-
-                case SortOrder.Custom:
-
-                    Logger.Debug("Sorting on custom field " + _propertyName);
-                    var yProp = y.GetType().GetProperty(_propertyName);
-                    var xProp = x.GetType().GetProperty(_propertyName);
-                    if (yProp == null || xProp == null) break;
-                    var yVal = yProp.GetValue(y, null);
-                    var xVal = xProp.GetValue(x,null);
-                    if (yVal == null && xVal == null) break;
-                    if (yVal == null) return 1;
-                    if (xVal == null) return -1;
-                    compare = String.Compare(xVal.ToString(), yVal.ToString(),_compareCulture);
-                    break;
-
-                default:
-                    compare = 0;
-                    break;
-            }
-
-            if (compare == 0) {
-
-                var name1 = x.SortName ?? x.Name ?? "";
-                var name2 = y.SortName ?? y.Name ?? "";
-
-                //if (Config.Instance.EnableAlphanumericSorting)
-                    compare = AlphaNumericCompare(name1, name2,_compareCulture);
-                //else
-                //    compare = String.Compare(name1,name2,_compareCulture);
-            }
-
-            return compare;
-        }
-
-
-        #endregion
-
-        /// <summary>
-        /// Alphas the numeric compare.
-        /// </summary>
-        /// <param name="s1">The s1.</param>
-        /// <param name="s2">The s2.</param>
-        /// <param name="compareCulture">The compare culture.</param>
-        /// <returns>System.Int32.</returns>
-        private int AlphaNumericCompare(string s1, string s2, StringComparison compareCulture) {
-            // http://dotnetperls.com/Content/Alphanumeric-Sorting.aspx
-
-            int len1 = s1.Length;
-            int len2 = s2.Length;
-            int marker1 = 0;
-            int marker2 = 0;
-
-            // Walk through two the strings with two markers.
-            while (marker1 < len1 && marker2 < len2) {
-                char ch1 = s1[marker1];
-                char ch2 = s2[marker2];
-
-                // Some buffers we can build up characters in for each chunk.
-                var space1 = new char[len1];
-                var loc1 = 0;
-                var space2 = new char[len2];
-                var loc2 = 0;
-
-                // Walk through all following characters that are digits or
-                // characters in BOTH strings starting at the appropriate marker.
-                // Collect char arrays.
-                do {
-                    space1[loc1++] = ch1;
-                    marker1++;
-
-                    if (marker1 < len1) {
-                        ch1 = s1[marker1];
-                    } else {
-                        break;
-                    }
-                } while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
-
-                do {
-                    space2[loc2++] = ch2;
-                    marker2++;
-
-                    if (marker2 < len2) {
-                        ch2 = s2[marker2];
-                    } else {
-                        break;
-                    }
-                } while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
-
-                // If we have collected numbers, compare them numerically.
-                // Otherwise, if we have strings, compare them alphabetically.
-                var str1 = new string(space1);
-                var str2 = new string(space2);
-                
-                var result = 0;
-
-                //biggest int - 2147483647
-                if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]) /*&& str1.Length < 10 && str2.Length < 10*/) //this assumed the entire string was a number...
-                {
-                    int thisNumericChunk;
-                    var isValid = false;
-
-                    if (int.TryParse(str1.Substring(0, str1.Length > 9 ? 10 : str1.Length), out thisNumericChunk))
-                    {
-                        int thatNumericChunk;
-                        
-                        if (int.TryParse(str2.Substring(0, str2.Length > 9 ? 10 : str2.Length), out thatNumericChunk))
-                        {
-                            isValid = true;
-                            result = thisNumericChunk.CompareTo(thatNumericChunk);
-                        }
-                    }
-                    
-                    if (!isValid)
-                    {
-                        Logger.Error("Error comparing numeric strings: " + str1 + "/" + str2);
-                        result = String.Compare(str1, str2, compareCulture);
-                    }
-                    
-                } else {
-                    result = String.Compare(str1,str2,compareCulture);
-                }
-
-                if (result != 0) {
-                    return result;
-                }
-            }
-            return len1 - len2;
-        }
-    }
-}

+ 17 - 0
MediaBrowser.Controller/Sorting/IBaseItemComparer.cs

@@ -0,0 +1,17 @@
+using MediaBrowser.Controller.Entities;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Sorting
+{
+    /// <summary>
+    /// Interface IBaseItemComparer
+    /// </summary>
+    public interface IBaseItemComparer : IComparer<BaseItem>
+    {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+    }
+}

+ 16 - 0
MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs

@@ -0,0 +1,16 @@
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Sorting
+{
+    /// <summary>
+    /// Represents a BaseItem comparer that requires a User to perform it's comparison
+    /// </summary>
+    public interface IUserBaseItemComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Gets or sets the user.
+        /// </summary>
+        /// <value>The user.</value>
+        User User { get; set; }
+    }
+}

+ 0 - 33
MediaBrowser.Controller/Sorting/SortOrder.cs

@@ -1,33 +0,0 @@
-
-namespace MediaBrowser.Controller.Sorting {
-    /// <summary>
-    /// Enum SortOrder
-    /// </summary>
-    public enum SortOrder {
-
-        /// <summary>
-        /// Sort by name
-        /// </summary>
-        Name,
-        /// <summary>
-        /// Sort by date added to the library
-        /// </summary>
-        Date,
-        /// <summary>
-        /// Sort by community rating
-        /// </summary>
-        Rating,
-        /// <summary>
-        /// Sort by runtime
-        /// </summary>
-        Runtime,
-        /// <summary>
-        /// Sort by year
-        /// </summary>
-        Year,
-        /// <summary>
-        /// Custom sort order added by plugins
-        /// </summary>
-        Custom
-    }
-}

+ 1 - 7
MediaBrowser.Installer/App.xaml.cs

@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
+using System.Windows;
 
 namespace MediaBrowser.Installer
 {

+ 0 - 4
MediaBrowser.Installer/MediaBrowser.Installer.csproj

@@ -87,15 +87,11 @@
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
-    <Reference Include="System.Data" />
     <Reference Include="System.Drawing" />
-    <Reference Include="System.Web" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml" />
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="System.Xaml">
       <RequiredTargetFramework>4.0</RequiredTargetFramework>
     </Reference>

+ 34 - 48
MediaBrowser.Model/DTO/BaseItemDto.cs

@@ -154,20 +154,6 @@ namespace MediaBrowser.Model.Dto
         [ProtoMember(25)]
         public bool IsFolder { get; set; }
 
-        /// <summary>
-        /// If the item is a Folder this will determine if it's the Root or not
-        /// </summary>
-        /// <value><c>null</c> if [is root] contains no value, <c>true</c> if [is root]; otherwise, <c>false</c>.</value>
-        [ProtoMember(26)]
-        public bool? IsRoot { get; set; }
-
-        /// <summary>
-        /// If the item is a Folder this will determine if it's a VF or not
-        /// </summary>
-        /// <value><c>null</c> if [is virtual folder] contains no value, <c>true</c> if [is virtual folder]; otherwise, <c>false</c>.</value>
-        [ProtoMember(27)]
-        public bool? IsVirtualFolder { get; set; }
-
         /// <summary>
         /// Gets or sets the parent id.
         /// </summary>
@@ -231,13 +217,6 @@ namespace MediaBrowser.Model.Dto
         [ProtoMember(36)]
         public UserItemDataDto UserData { get; set; }
 
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is new.
-        /// </summary>
-        /// <value><c>true</c> if this instance is new; otherwise, <c>false</c>.</value>
-        [ProtoMember(37)]
-        public bool IsNew { get; set; }
-
         /// <summary>
         /// Gets or sets the recently added item count.
         /// </summary>
@@ -315,13 +294,6 @@ namespace MediaBrowser.Model.Dto
         [ProtoMember(52)]
         public List<DayOfWeek> AirDays { get; set; }
 
-        /// <summary>
-        /// Gets or sets the sort options.
-        /// </summary>
-        /// <value>The sort options.</value>
-        [ProtoMember(53)]
-        public string[] SortOptions { get; set; }
-
         /// <summary>
         /// Gets or sets the index options.
         /// </summary>
@@ -398,26 +370,6 @@ namespace MediaBrowser.Model.Dto
             return Type.Equals(type, StringComparison.OrdinalIgnoreCase);
         }
 
-        /// <summary>
-        /// Gets a value indicating whether this instance can resume.
-        /// </summary>
-        /// <value><c>true</c> if this instance can resume; otherwise, <c>false</c>.</value>
-        [IgnoreDataMember]
-        public bool CanResume
-        {
-            get { return UserData != null && UserData.PlaybackPositionTicks > 0; }
-        }
-
-        /// <summary>
-        /// Gets the resume position ticks.
-        /// </summary>
-        /// <value>The resume position ticks.</value>
-        [IgnoreDataMember]
-        public long ResumePositionTicks
-        {
-            get { return UserData == null ? 0 : UserData.PlaybackPositionTicks; }
-        }
-
         /// <summary>
         /// Gets or sets the image tags.
         /// </summary>
@@ -474,6 +426,26 @@ namespace MediaBrowser.Model.Dto
         [ProtoMember(69)]
         public string MediaType { get; set; }
 
+        /// <summary>
+        /// Gets a value indicating whether this instance can resume.
+        /// </summary>
+        /// <value><c>true</c> if this instance can resume; otherwise, <c>false</c>.</value>
+        [IgnoreDataMember]
+        public bool CanResume
+        {
+            get { return UserData != null && UserData.PlaybackPositionTicks > 0; }
+        }
+
+        /// <summary>
+        /// Gets the resume position ticks.
+        /// </summary>
+        /// <value>The resume position ticks.</value>
+        [IgnoreDataMember]
+        public long ResumePositionTicks
+        {
+            get { return UserData == null ? 0 : UserData.PlaybackPositionTicks; }
+        }
+
         /// <summary>
         /// Gets the backdrop count.
         /// </summary>
@@ -604,12 +576,26 @@ namespace MediaBrowser.Model.Dto
             get { return string.Equals(MediaType, Entities.MediaType.Game, StringComparison.OrdinalIgnoreCase); }
         }
 
+        /// <summary>
+        /// Gets a value indicating whether this instance is person.
+        /// </summary>
+        /// <value><c>true</c> if this instance is person; otherwise, <c>false</c>.</value>
         [IgnoreDataMember]
         public bool IsPerson
         {
             get { return string.Equals(Type, "Person", StringComparison.OrdinalIgnoreCase); }
         }
 
+        /// <summary>
+        /// Gets a value indicating whether this instance is root.
+        /// </summary>
+        /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
+        [IgnoreDataMember]
+        public bool IsRoot
+        {
+            get { return string.Equals(Type, "AggregateFolder", StringComparison.OrdinalIgnoreCase); }
+        }
+
         /// <summary>
         /// Occurs when [property changed].
         /// </summary>

+ 0 - 42
MediaBrowser.Model/DTO/ItemSortBy.cs

@@ -1,42 +0,0 @@
-
-namespace MediaBrowser.Model.Dto
-{
-    /// <summary>
-    /// Enum ItemSortBy
-    /// </summary>
-    public enum ItemSortBy
-    {
-        /// <summary>
-        /// The album
-        /// </summary>
-        Album,
-        /// <summary>
-        /// The album artist
-        /// </summary>
-        AlbumArtist,
-        /// <summary>
-        /// The artist
-        /// </summary>
-        Artist,
-        /// <summary>
-        /// The date created
-        /// </summary>
-        DateCreated,
-        /// <summary>
-        /// The date played
-        /// </summary>
-        DatePlayed,
-        /// <summary>
-        /// The premiere date
-        /// </summary>
-        PremiereDate,
-        /// <summary>
-        /// The sort name
-        /// </summary>
-        SortName,
-        /// <summary>
-        /// The random
-        /// </summary>
-        Random
-    }
-}

+ 0 - 22
MediaBrowser.Model/DTO/MediaType.cs

@@ -1,22 +0,0 @@
-
-namespace MediaBrowser.Model.Dto
-{
-    /// <summary>
-    /// Enum MediaType
-    /// </summary>
-    public enum MediaType
-    {
-        /// <summary>
-        /// The audio
-        /// </summary>
-        Audio,
-        /// <summary>
-        /// The game
-        /// </summary>
-        Game,
-        /// <summary>
-        /// The video
-        /// </summary>
-        Video
-    }
-}

+ 1 - 1
MediaBrowser.Model/Entities/ImageType.cs

@@ -49,6 +49,6 @@ namespace MediaBrowser.Model.Entities
         /// <summary>
         /// The chapter image
         /// </summary>
-        ChapterImage
+        Chapter
     }
 }

+ 6 - 7
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -45,8 +45,7 @@
     <Compile Include="Dto\BaseItemPerson.cs" />
     <Compile Include="Dto\ChapterInfoDto.cs" />
     <Compile Include="Dto\IItemDto.cs" />
-    <Compile Include="Dto\ItemsByNameQuery.cs" />
-    <Compile Include="Dto\MediaType.cs" />
+    <Compile Include="Querying\ItemsByNameQuery.cs" />
     <Compile Include="Entities\BaseItemInfo.cs" />
     <Compile Include="Connectivity\ClientConnectionInfo.cs" />
     <Compile Include="Connectivity\ClientType.cs" />
@@ -63,8 +62,8 @@
     <Compile Include="Globalization\CultureDto.cs" />
     <Compile Include="IO\FileSystemEntryInfo.cs" />
     <Compile Include="Dto\ImageOptions.cs" />
-    <Compile Include="Dto\ItemFilter.cs" />
-    <Compile Include="Dto\ItemQuery.cs" />
+    <Compile Include="Querying\ItemFilter.cs" />
+    <Compile Include="Querying\ItemQuery.cs" />
     <Compile Include="Entities\LibraryUpdateInfo.cs" />
     <Compile Include="Entities\ParentalRating.cs" />
     <Compile Include="Dto\StreamOptions.cs" />
@@ -90,11 +89,11 @@
     <Compile Include="Configuration\UserConfiguration.cs" />
     <Compile Include="Drawing\DrawingUtils.cs" />
     <Compile Include="Dto\UserItemDataDto.cs" />
-    <Compile Include="Dto\ItemFields.cs" />
-    <Compile Include="Dto\ItemSortBy.cs" />
+    <Compile Include="Querying\ItemFields.cs" />
+    <Compile Include="Querying\ItemSortBy.cs" />
     <Compile Include="Dto\BaseItemDto.cs" />
     <Compile Include="Dto\UserDto.cs" />
-    <Compile Include="Dto\ItemsResult.cs" />
+    <Compile Include="Querying\ItemsResult.cs" />
     <Compile Include="Entities\DisplayPreferences.cs" />
     <Compile Include="Entities\ImageType.cs" />
     <Compile Include="Entities\IHasProviderIds.cs" />

+ 54 - 0
MediaBrowser.Model/PropertyChanged.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<doc>
+    <assembly>
+        <name>PropertyChanged</name>
+    </assembly>
+    <members>
+        <member name="T:PropertyChanged.AlsoNotifyForAttribute">
+            <summary>
+            Injects this property to be notified when a dependant property is set.
+            </summary>
+        </member>
+        <member name="M:PropertyChanged.AlsoNotifyForAttribute.#ctor(System.String)">
+            <summary>
+             Initializes a new instance of <see cref="T:PropertyChanged.DependsOnAttribute"/>.
+            </summary>
+            <param name="property">A property that will be notified for.</param>
+        </member>
+        <member name="M:PropertyChanged.AlsoNotifyForAttribute.#ctor(System.String,System.String[])">
+            <summary>
+             Initializes a new instance of <see cref="T:PropertyChanged.DependsOnAttribute"/>.
+            </summary>
+            <param name="property">A property that will be notified for.</param>
+            <param name="otherProperties">The properties that will be notified for.</param>
+        </member>
+        <member name="T:PropertyChanged.DependsOnAttribute">
+            <summary>
+            Injects this property to be notified when a dependant property is set.
+            </summary>
+        </member>
+        <member name="M:PropertyChanged.DependsOnAttribute.#ctor(System.String)">
+            <summary>
+             Initializes a new instance of <see cref="T:PropertyChanged.DependsOnAttribute"/>.
+            </summary>
+            <param name="dependency">A property that the assigned property depends on.</param>
+        </member>
+        <member name="M:PropertyChanged.DependsOnAttribute.#ctor(System.String,System.String[])">
+            <summary>
+             Initializes a new instance of <see cref="T:PropertyChanged.DependsOnAttribute"/>.
+            </summary>
+            <param name="dependency">A property that the assigned property depends on.</param>
+            <param name="otherDependencies">The properties that the assigned property depends on.</param>
+        </member>
+        <member name="T:PropertyChanged.DoNotNotifyAttribute">
+            <summary>
+            Exclude a <see cref="T:System.Type"/> or property from notification.
+            </summary>
+        </member>
+        <member name="T:PropertyChanged.DoNotSetChangedAttribute">
+            <summary>
+            Exclude a <see cref="T:System.Type"/> or property from IsChanged flagging.
+            </summary>
+        </member>
+    </members>
+</doc>

+ 1 - 6
MediaBrowser.Model/DTO/ItemFields.cs → MediaBrowser.Model/Querying/ItemFields.cs

@@ -1,5 +1,5 @@
 
-namespace MediaBrowser.Model.Dto
+namespace MediaBrowser.Model.Querying
 {
     /// <summary>
     /// Used to control the data that gets attached to DtoBaseItems
@@ -86,11 +86,6 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         SortName,
 
-        /// <summary>
-        /// The fields that the server supports sorting on
-        /// </summary>
-        SortOptions,
-
         /// <summary>
         /// The studios of the item
         /// </summary>

+ 1 - 1
MediaBrowser.Model/DTO/ItemFilter.cs → MediaBrowser.Model/Querying/ItemFilter.cs

@@ -1,5 +1,5 @@
 
-namespace MediaBrowser.Model.Dto
+namespace MediaBrowser.Model.Querying
 {
     /// <summary>
     /// Enum ItemFilter

+ 4 - 9
MediaBrowser.Model/DTO/ItemQuery.cs → MediaBrowser.Model/Querying/ItemQuery.cs

@@ -1,7 +1,8 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
 using System;
 
-namespace MediaBrowser.Model.Dto
+namespace MediaBrowser.Model.Querying
 {
     /// <summary>
     /// Contains all the possible parameters that can be used to query for items
@@ -36,7 +37,7 @@ namespace MediaBrowser.Model.Dto
         /// What to sort the results by
         /// </summary>
         /// <value>The sort by.</value>
-        public ItemSortBy[] SortBy { get; set; }
+        public string[] SortBy { get; set; }
 
         /// <summary>
         /// The sort order to return results with
@@ -116,12 +117,6 @@ namespace MediaBrowser.Model.Dto
         /// <value>The index by.</value>
         public string IndexBy { get; set; }
 
-        /// <summary>
-        /// The dynamic, localized sort function name
-        /// </summary>
-        /// <value>The dynamic sort by.</value>
-        public string DynamicSortBy { get; set; }
-
         /// <summary>
         /// Gets or sets the image types.
         /// </summary>

+ 54 - 0
MediaBrowser.Model/Querying/ItemSortBy.cs

@@ -0,0 +1,54 @@
+
+namespace MediaBrowser.Model.Querying
+{
+    /// <summary>
+    /// These represent sort orders that are known by the core
+    /// </summary>
+    public static class ItemSortBy
+    {
+        /// <summary>
+        /// The album
+        /// </summary>
+        public const string Album = "Album";
+        /// <summary>
+        /// The album artist
+        /// </summary>
+        public const string AlbumArtist = "AlbumArtist";
+        /// <summary>
+        /// The artist
+        /// </summary>
+        public const string Artist = "Artist";
+        /// <summary>
+        /// The date created
+        /// </summary>
+        public const string DateCreated = "DateCreated";
+        /// <summary>
+        /// The date played
+        /// </summary>
+        public const string DatePlayed = "DatePlayed";
+        /// <summary>
+        /// The premiere date
+        /// </summary>
+        public const string PremiereDate = "PremiereDate";
+        /// <summary>
+        /// The sort name
+        /// </summary>
+        public const string SortName = "SortName";
+        /// <summary>
+        /// The random
+        /// </summary>
+        public const string Random = "Random";
+        /// <summary>
+        /// The runtime
+        /// </summary>
+        public const string Runtime = "Runtime";
+        /// <summary>
+        /// The community rating
+        /// </summary>
+        public const string CommunityRating = "CommunityRating";
+        /// <summary>
+        /// The production year
+        /// </summary>
+        public const string ProductionYear = "ProductionYear";
+    }
+}

+ 4 - 4
MediaBrowser.Model/Dto/ItemsByNameQuery.cs → MediaBrowser.Model/Querying/ItemsByNameQuery.cs

@@ -1,7 +1,7 @@
 using MediaBrowser.Model.Entities;
 using System;
 
-namespace MediaBrowser.Model.Dto
+namespace MediaBrowser.Model.Querying
 {
     /// <summary>
     /// Class ItemsByNameQuery
@@ -34,10 +34,10 @@ namespace MediaBrowser.Model.Dto
         /// <value>The sort order.</value>
         public SortOrder? SortOrder { get; set; }
         /// <summary>
-        /// If specified the search will be localized within a specific item or folder
+        /// Gets or sets the parent id.
         /// </summary>
-        /// <value>The item id.</value>
-        public string ItemId { get; set; }
+        /// <value>The parent id.</value>
+        public string ParentId { get; set; }
         /// <summary>
         /// Fields to return within the items, in addition to basic information
         /// </summary>

+ 3 - 2
MediaBrowser.Model/DTO/ItemsResult.cs → MediaBrowser.Model/Querying/ItemsResult.cs

@@ -1,6 +1,7 @@
-using ProtoBuf;
+using MediaBrowser.Model.Dto;
+using ProtoBuf;
 
-namespace MediaBrowser.Model.Dto
+namespace MediaBrowser.Model.Querying
 {
     /// <summary>
     /// Represents the result of a query for items

+ 12 - 2
MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Extensions;
+using System.Net;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
@@ -257,7 +258,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
                 var stream = await factoryFn().ConfigureAwait(false);
 
-                return new StreamWriter(stream);
+                var httpListenerResponse = (HttpListenerResponse) Response.OriginalResponse;
+                httpListenerResponse.SendChunked = false;
+
+                if (IsRangeRequest)
+                {
+                    return new RangeRequestWriter(Request.Headers, httpListenerResponse, stream);
+                }
+
+                httpListenerResponse.ContentLength64 = stream.Length;
+                return new StreamWriter(stream, Logger);
             }
 
             string content;

+ 177 - 0
MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs

@@ -0,0 +1,177 @@
+using ServiceStack.Service;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.HttpServer
+{
+    public class RangeRequestWriter : IStreamWriter
+    {
+        /// <summary>
+        /// Gets or sets the source stream.
+        /// </summary>
+        /// <value>The source stream.</value>
+        public Stream SourceStream { get; set; }
+        public HttpListenerResponse Response { get; set; }
+        public NameValueCollection RequestHeaders { get; set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StreamWriter" /> class.
+        /// </summary>
+        /// <param name="requestHeaders">The request headers.</param>
+        /// <param name="response">The response.</param>
+        /// <param name="source">The source.</param>
+        public RangeRequestWriter(NameValueCollection requestHeaders, HttpListenerResponse response, Stream source)
+        {
+            RequestHeaders = requestHeaders;
+            Response = response;
+            SourceStream = source;
+        }
+
+        /// <summary>
+        /// The _requested ranges
+        /// </summary>
+        private List<KeyValuePair<long, long?>> _requestedRanges;
+        /// <summary>
+        /// Gets the requested ranges.
+        /// </summary>
+        /// <value>The requested ranges.</value>
+        protected IEnumerable<KeyValuePair<long, long?>> RequestedRanges
+        {
+            get
+            {
+                if (_requestedRanges == null)
+                {
+                    _requestedRanges = new List<KeyValuePair<long, long?>>();
+
+                    // Example: bytes=0-,32-63
+                    var ranges = RequestHeaders["Range"].Split('=')[1].Split(',');
+
+                    foreach (var range in ranges)
+                    {
+                        var vals = range.Split('-');
+
+                        long start = 0;
+                        long? end = null;
+
+                        if (!string.IsNullOrEmpty(vals[0]))
+                        {
+                            start = long.Parse(vals[0]);
+                        }
+                        if (!string.IsNullOrEmpty(vals[1]))
+                        {
+                            end = long.Parse(vals[1]);
+                        }
+
+                        _requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
+                    }
+                }
+
+                return _requestedRanges;
+            }
+        }
+
+        /// <summary>
+        /// Writes to.
+        /// </summary>
+        /// <param name="responseStream">The response stream.</param>
+        public void WriteTo(Stream responseStream)
+        {
+            Response.Headers["Accept-Ranges"] = "bytes";
+            Response.StatusCode = 206;
+            
+            var task = WriteToAsync(responseStream);
+
+            Task.WaitAll(task);
+        }
+
+        /// <summary>
+        /// Writes to async.
+        /// </summary>
+        /// <param name="responseStream">The response stream.</param>
+        /// <returns>Task.</returns>
+        private async Task WriteToAsync(Stream responseStream)
+        {
+            using (var source = SourceStream)
+            {
+                var requestedRange = RequestedRanges.First();
+
+                var totalLength = SourceStream.Length;
+
+                // If the requested range is "0-", we can optimize by just doing a stream copy
+                if (!requestedRange.Value.HasValue)
+                {
+                    await ServeCompleteRangeRequest(source, requestedRange, responseStream, totalLength).ConfigureAwait(false);
+                }
+
+                // This will have to buffer a portion of the content into memory
+                await ServePartialRangeRequest(source, requestedRange.Key, requestedRange.Value.Value, responseStream, totalLength).ConfigureAwait(false);
+            }
+        }
+
+        /// <summary>
+        /// Handles a range request of "bytes=0-"
+        /// This will serve the complete content and add the content-range header
+        /// </summary>
+        /// <param name="sourceStream">The source stream.</param>
+        /// <param name="requestedRange">The requested range.</param>
+        /// <param name="responseStream">The response stream.</param>
+        /// <param name="totalContentLength">Total length of the content.</param>
+        /// <returns>Task.</returns>
+        private Task ServeCompleteRangeRequest(Stream sourceStream, KeyValuePair<long, long?> requestedRange, Stream responseStream, long totalContentLength)
+        {
+            var rangeStart = requestedRange.Key;
+            var rangeEnd = totalContentLength - 1;
+            var rangeLength = 1 + rangeEnd - rangeStart;
+
+            // Content-Length is the length of what we're serving, not the original content
+            Response.ContentLength64 = rangeLength;
+            Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
+
+            if (rangeStart > 0)
+            {
+                sourceStream.Position = rangeStart;
+            }
+
+            return sourceStream.CopyToAsync(responseStream);
+        }
+
+        /// <summary>
+        /// Serves a partial range request
+        /// </summary>
+        /// <param name="sourceStream">The source stream.</param>
+        /// <param name="rangeStart">The range start.</param>
+        /// <param name="rangeEnd">The range end.</param>
+        /// <param name="responseStream">The response stream.</param>
+        /// <param name="totalContentLength">Total length of the content.</param>
+        /// <returns>Task.</returns>
+        private async Task ServePartialRangeRequest(Stream sourceStream, long rangeStart, long rangeEnd, Stream responseStream, long totalContentLength)
+        {
+            var rangeLength = 1 + rangeEnd - rangeStart;
+
+            // Content-Length is the length of what we're serving, not the original content
+            Response.ContentLength64 = rangeLength;
+            Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
+
+            sourceStream.Position = rangeStart;
+
+            // Fast track to just copy the stream to the end
+            if (rangeEnd == totalContentLength - 1)
+            {
+                await sourceStream.CopyToAsync(responseStream).ConfigureAwait(false);
+            }
+            else
+            {
+                // Read the bytes we need
+                var buffer = new byte[Convert.ToInt32(rangeLength)];
+                await sourceStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+
+                await responseStream.WriteAsync(buffer, 0, Convert.ToInt32(rangeLength)).ConfigureAwait(false);
+            }
+        }
+    }
+}

+ 19 - 4
MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs

@@ -1,4 +1,6 @@
-using ServiceStack.Service;
+using MediaBrowser.Model.Logging;
+using ServiceStack.Service;
+using System;
 using System.IO;
 using System.Threading.Tasks;
 
@@ -9,6 +11,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
     /// </summary>
     public class StreamWriter : IStreamWriter
     {
+        private ILogger Logger { get; set; }
+        
         /// <summary>
         /// Gets or sets the source stream.
         /// </summary>
@@ -19,9 +23,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// Initializes a new instance of the <see cref="StreamWriter" /> class.
         /// </summary>
         /// <param name="source">The source.</param>
-        public StreamWriter(Stream source)
+        /// <param name="logger">The logger.</param>
+        public StreamWriter(Stream source, ILogger logger)
         {
             SourceStream = source;
+            Logger = logger;
         }
 
         /// <summary>
@@ -42,9 +48,18 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <returns>Task.</returns>
         private async Task WriteToAsync(Stream responseStream)
         {
-            using (var src = SourceStream)
+            try
             {
-                await src.CopyToAsync(responseStream).ConfigureAwait(false);
+                using (var src = SourceStream)
+                {
+                    await src.CopyToAsync(responseStream).ConfigureAwait(false);
+                }
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error streaming media", ex);
+
+                throw;
             }
         }
     }

+ 68 - 1
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -9,6 +9,7 @@ using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.ScheduledTasks;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Server.Implementations.Library.Resolvers;
@@ -22,6 +23,7 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using SortOrder = MediaBrowser.Model.Entities.SortOrder;
 
 namespace MediaBrowser.Server.Implementations.Library
 {
@@ -54,6 +56,12 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <value>The entity resolvers enumerable.</value>
         private IEnumerable<IItemResolver> EntityResolvers { get; set; }
 
+        /// <summary>
+        /// Gets or sets the comparers.
+        /// </summary>
+        /// <value>The comparers.</value>
+        private IEnumerable<IBaseItemComparer> Comparers { get; set; }
+
         #region LibraryChanged Event
         /// <summary>
         /// Fires whenever any validation routine adds or removes items.  The added and removed items are properties of the args.
@@ -124,12 +132,14 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="pluginFolders">The plugin folders.</param>
         /// <param name="resolvers">The resolvers.</param>
         /// <param name="introProviders">The intro providers.</param>
-        public void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders)
+        /// <param name="itemComparers">The item comparers.</param>
+        public void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers)
         {
             EntityResolutionIgnoreRules = rules;
             PluginFolderCreators = pluginFolders;
             EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
             IntroProviders = introProviders;
+            Comparers = itemComparers;
         }
 
         /// <summary>
@@ -702,5 +712,62 @@ namespace MediaBrowser.Server.Implementations.Library
         {
             return IntroProviders.SelectMany(i => i.GetIntros(item, user));
         }
+
+        /// <summary>
+        /// Sorts the specified sort by.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <param name="user">The user.</param>
+        /// <param name="sortBy">The sort by.</param>
+        /// <param name="sortOrder">The sort order.</param>
+        /// <returns>IEnumerable{BaseItem}.</returns>
+        public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder)
+        {
+            var isFirst = true;
+
+            IOrderedEnumerable<BaseItem> orderedItems = null;
+
+            foreach (var orderBy in sortBy.Select(o => GetComparer(o, user)).Where(c => c != null))
+            {
+                if (isFirst)
+                {
+                    orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, orderBy) : items.OrderBy(i => i, orderBy);
+                }
+                else
+                {
+                    orderedItems = sortOrder == SortOrder.Descending ? orderedItems.ThenByDescending(i => i, orderBy) : orderedItems.ThenBy(i => i, orderBy);
+                }
+
+                isFirst = false;
+            }
+
+            return orderedItems ?? items;
+        }
+
+        /// <summary>
+        /// Gets the comparer.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>IBaseItemComparer.</returns>
+        private IBaseItemComparer GetComparer(string name, User user)
+        {
+            var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase));
+
+            if (comparer != null)
+            {
+                // If it requires a user, create a new one, and assign the user
+                if (comparer is IUserBaseItemComparer)
+                {
+                    var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType());
+
+                    userComparer.User = user;
+
+                    return userComparer;
+                }
+            }
+
+            return comparer;
+        }
     }
 }

+ 12 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -109,6 +109,7 @@
     <Compile Include="HttpServer\HttpResultFactory.cs" />
     <Compile Include="HttpServer\HttpServer.cs" />
     <Compile Include="HttpServer\NativeWebSocket.cs" />
+    <Compile Include="HttpServer\RangeRequestWriter.cs" />
     <Compile Include="HttpServer\ServerFactory.cs" />
     <Compile Include="HttpServer\StreamWriter.cs" />
     <Compile Include="HttpServer\SwaggerService.cs" />
@@ -139,6 +140,17 @@
     <Compile Include="ServerApplicationPaths.cs" />
     <Compile Include="ServerManager\ServerManager.cs" />
     <Compile Include="ServerManager\WebSocketConnection.cs" />
+    <Compile Include="Sorting\AlbumArtistComparer.cs" />
+    <Compile Include="Sorting\AlbumComparer.cs" />
+    <Compile Include="Sorting\ArtistComparer.cs" />
+    <Compile Include="Sorting\CommunityRatingComparer.cs" />
+    <Compile Include="Sorting\DateCreatedComparer.cs" />
+    <Compile Include="Sorting\DatePlayedComparer.cs" />
+    <Compile Include="Sorting\PremiereDateComparer.cs" />
+    <Compile Include="Sorting\ProductionYearComparer.cs" />
+    <Compile Include="Sorting\RandomComparer.cs" />
+    <Compile Include="Sorting\RuntimeComparer.cs" />
+    <Compile Include="Sorting\SortNameComparer.cs" />
     <Compile Include="Sqlite\SQLiteDisplayPreferencesRepository.cs" />
     <Compile Include="Sqlite\SQLiteExtensions.cs" />
     <Compile Include="Sqlite\SQLiteItemRepository.cs" />

+ 46 - 0
MediaBrowser.Server.Implementations/Sorting/AlbumArtistComparer.cs

@@ -0,0 +1,46 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class AlbumArtistComparer
+    /// </summary>
+    public class AlbumArtistComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
+        }
+
+        /// <summary>
+        /// Gets the value.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <returns>System.String.</returns>
+        private string GetValue(BaseItem x)
+        {
+            var audio = x as Audio;
+
+            return audio == null ? string.Empty : audio.AlbumArtist;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.AlbumArtist; }
+        }
+    }
+}

+ 46 - 0
MediaBrowser.Server.Implementations/Sorting/AlbumComparer.cs

@@ -0,0 +1,46 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class AlbumComparer
+    /// </summary>
+    public class AlbumComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
+        }
+
+        /// <summary>
+        /// Gets the value.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <returns>System.String.</returns>
+        private string GetValue(BaseItem x)
+        {
+            var audio = x as Audio;
+
+            return audio == null ? string.Empty : audio.Album;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.Album; }
+        }
+    }
+}

+ 46 - 0
MediaBrowser.Server.Implementations/Sorting/ArtistComparer.cs

@@ -0,0 +1,46 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class ArtistComparer
+    /// </summary>
+    public class ArtistComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
+        }
+
+        /// <summary>
+        /// Gets the value.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <returns>System.String.</returns>
+        private string GetValue(BaseItem x)
+        {
+            var audio = x as Audio;
+
+            return audio == null ? string.Empty : audio.Artist;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.Artist; }
+        }
+    }
+}

+ 29 - 0
MediaBrowser.Server.Implementations/Sorting/CommunityRatingComparer.cs

@@ -0,0 +1,29 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    public class CommunityRatingComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return (x.CommunityRating ?? 0).CompareTo(y.CommunityRating ?? 0);
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.CommunityRating; }
+        }
+    }
+}

+ 33 - 0
MediaBrowser.Server.Implementations/Sorting/DateCreatedComparer.cs

@@ -0,0 +1,33 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class DateCreatedComparer
+    /// </summary>
+    public class DateCreatedComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return DateTime.Compare(x.DateCreated, y.DateCreated);
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.DateCreated; }
+        }
+    }
+}

+ 56 - 0
MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs

@@ -0,0 +1,56 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class DatePlayedComparer
+    /// </summary>
+    public class DatePlayedComparer : IUserBaseItemComparer
+    {
+        /// <summary>
+        /// Gets or sets the user.
+        /// </summary>
+        /// <value>The user.</value>
+        public User User { get; set; }
+
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return GetDate(x).CompareTo(GetDate(y));
+        }
+
+        /// <summary>
+        /// Gets the date.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <returns>DateTime.</returns>
+        private DateTime GetDate(BaseItem x)
+        {
+            var userdata = x.GetUserData(User, false);
+
+            if (userdata != null && userdata.LastPlayedDate.HasValue)
+            {
+                return userdata.LastPlayedDate.Value;
+            }
+
+            return DateTime.MaxValue;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.DatePlayed; }
+        }
+    }
+}

+ 52 - 0
MediaBrowser.Server.Implementations/Sorting/PremiereDateComparer.cs

@@ -0,0 +1,52 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class PremiereDateComparer
+    /// </summary>
+    public class PremiereDateComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return GetDate(x).CompareTo(GetDate(y));
+        }
+
+        /// <summary>
+        /// Gets the date.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <returns>DateTime.</returns>
+        private DateTime GetDate(BaseItem x)
+        {
+            if (x.PremiereDate.HasValue)
+            {
+                return x.PremiereDate.Value;
+            }
+            
+            if (x.ProductionYear.HasValue)
+            {
+                return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+            }
+            return DateTime.MaxValue;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.PremiereDate; }
+        }
+    }
+}

+ 52 - 0
MediaBrowser.Server.Implementations/Sorting/ProductionYearComparer.cs

@@ -0,0 +1,52 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class ProductionYearComparer
+    /// </summary>
+    public class ProductionYearComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return GetValue(x).CompareTo(GetValue(y));
+        }
+
+        /// <summary>
+        /// Gets the date.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <returns>DateTime.</returns>
+        private int GetValue(BaseItem x)
+        {
+            if (x.ProductionYear.HasValue)
+            {
+                return x.ProductionYear.Value;
+            }
+
+            if (x.PremiereDate.HasValue)
+            {
+                return x.PremiereDate.Value.Year;
+            }
+
+            return 0;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.ProductionYear; }
+        }
+    }
+}

+ 33 - 0
MediaBrowser.Server.Implementations/Sorting/RandomComparer.cs

@@ -0,0 +1,33 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class RandomComparer
+    /// </summary>
+    public class RandomComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return Guid.NewGuid().CompareTo(Guid.NewGuid());
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.Random; }
+        }
+    }
+}

+ 32 - 0
MediaBrowser.Server.Implementations/Sorting/RuntimeComparer.cs

@@ -0,0 +1,32 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class RuntimeComparer
+    /// </summary>
+    public class RuntimeComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return (x.RunTimeTicks ?? 0).CompareTo(y.RunTimeTicks ?? 0);
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.Runtime; }
+        }
+    }
+}

+ 33 - 0
MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs

@@ -0,0 +1,33 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    /// <summary>
+    /// Class SortNameComparer
+    /// </summary>
+    public class SortNameComparer : IBaseItemComparer
+    {
+        /// <summary>
+        /// Compares the specified x.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <param name="y">The y.</param>
+        /// <returns>System.Int32.</returns>
+        public int Compare(BaseItem x, BaseItem y)
+        {
+            return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase);
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.SortName; }
+        }
+    }
+}

+ 4 - 1
MediaBrowser.Server.Implementations/Udp/UdpServer.cs

@@ -127,7 +127,10 @@ namespace MediaBrowser.Server.Implementations.Udp
         /// </summary>
         public void Stop()
         {
-            _udpClient.Close();
+            if (_udpClient != null)
+            {
+                _udpClient.Close();
+            }
         }
 
         /// <summary>

+ 29 - 22
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -18,6 +18,7 @@ using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Updates;
 using MediaBrowser.Controller.Weather;
 using MediaBrowser.IsoMounter;
@@ -126,23 +127,21 @@ namespace MediaBrowser.ServerApplication
         /// </summary>
         /// <value>The HTTP server.</value>
         private IHttpServer HttpServer { get; set; }
- 
+
         /// <summary>
-        /// Inits this instance.
+        /// Runs the startup tasks.
         /// </summary>
         /// <returns>Task.</returns>
-        public override async Task Init()
+        protected override async Task RunStartupTasks()
         {
-            await base.Init().ConfigureAwait(false);
+            // Do these before allowing the base method to run, which will invoke startup scheduled tasks
+            await ServerKernel.LoadRepositories(ServerConfigurationManager).ConfigureAwait(false);
 
-            Task.Run(async () =>
-            {
-                await ServerKernel.LoadRepositories(ServerConfigurationManager).ConfigureAwait(false);
+            await base.RunStartupTasks().ConfigureAwait(false);
 
-                DirectoryWatchers.Start();
+            DirectoryWatchers.Start();
 
-                Parallel.ForEach(GetExports<IServerEntryPoint>(), entryPoint => entryPoint.Run());
-            });
+            Parallel.ForEach(GetExports<IServerEntryPoint>(), entryPoint => entryPoint.Run());
         }
 
         /// <summary>
@@ -205,13 +204,15 @@ namespace MediaBrowser.ServerApplication
             ServerKernel.FFMpegManager = new FFMpegManager(ServerKernel, ZipClient, JsonSerializer, ProtobufSerializer, LogManager, ApplicationPaths);
             ServerKernel.ImageManager = new ImageManager(ServerKernel, ProtobufSerializer, LogManager.GetLogger("ImageManager"), ApplicationPaths);
 
-            ServerKernel.UserDataRepositories = GetExports<IUserDataRepository>();
-            ServerKernel.UserRepositories = GetExports<IUserRepository>();
-            ServerKernel.DisplayPreferencesRepositories = GetExports<IDisplayPreferencesRepository>();
-            ServerKernel.ItemRepositories = GetExports<IItemRepository>();
-            ServerKernel.WeatherProviders = GetExports<IWeatherProvider>();
-            ServerKernel.ImageEnhancers = GetExports<IImageEnhancer>().OrderBy(e => e.Priority).ToArray();
-            ServerKernel.StringFiles = GetExports<LocalizedStringData>();
+            Parallel.Invoke(
+                () => ServerKernel.UserDataRepositories = GetExports<IUserDataRepository>(),
+                () => ServerKernel.UserRepositories = GetExports<IUserRepository>(),
+                () => ServerKernel.DisplayPreferencesRepositories = GetExports<IDisplayPreferencesRepository>(),
+                () => ServerKernel.ItemRepositories = GetExports<IItemRepository>(),
+                () => ServerKernel.WeatherProviders = GetExports<IWeatherProvider>(),
+                () => ServerKernel.ImageEnhancers = GetExports<IImageEnhancer>().OrderBy(e => e.Priority).ToArray(),
+                () => ServerKernel.StringFiles = GetExports<LocalizedStringData>()
+                );
         }
 
         /// <summary>
@@ -237,14 +238,20 @@ namespace MediaBrowser.ServerApplication
         {
             base.FindParts();
 
-            HttpServer.Init(GetExports<IRestfulService>(false));
+            Parallel.Invoke(
+                
+                () =>
+                    {
+                        HttpServer.Init(GetExports<IRestfulService>(false));
 
-            ServerManager.AddWebSocketListeners(GetExports<IWebSocketListener>(false));
-            ServerManager.Start();
+                        ServerManager.AddWebSocketListeners(GetExports<IWebSocketListener>(false));
+                        ServerManager.Start();
+                    },
 
-            LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(), GetExports<IVirtualFolderCreator>(), GetExports<IItemResolver>(), GetExports<IIntroProvider>());
+                () => LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(), GetExports<IVirtualFolderCreator>(), GetExports<IItemResolver>(), GetExports<IIntroProvider>(), GetExports<IBaseItemComparer>()),
 
-            ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().OrderBy(e => e.Priority).ToArray());
+                () => ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().OrderBy(e => e.Priority).ToArray())
+                );
         }
 
         /// <summary>

+ 53 - 10
MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs

@@ -4,8 +4,10 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Serialization;
 using System;
 using System.Collections.Generic;
@@ -78,7 +80,8 @@ namespace MediaBrowser.ServerApplication
             Cursor = Cursors.Wait;
             await Task.Run(() =>
                 {
-                    IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? _libraryManager.RootFolder.Children.OrderBy(i => i.SortName) : _libraryManager.RootFolder.GetChildren(CurrentUser, sortBy: LocalizedStrings.Instance.GetString("NameDispPref"));
+                    IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? _libraryManager.RootFolder.Children : _libraryManager.RootFolder.GetChildren(CurrentUser);
+                    children = OrderByName(children, CurrentUser);
 
                     foreach (Folder folder in children)
                                    {
@@ -86,9 +89,12 @@ namespace MediaBrowser.ServerApplication
                                         var currentFolder = folder;
                                        Task.Factory.StartNew(() =>
                                         {
-                                            var prefs = ddlProfile.SelectedItem != null ? currentFolder.GetDisplayPrefs(ddlProfile.SelectedItem as User, false) ?? new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")} : new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")};
+                                            var prefs = ddlProfile.SelectedItem != null ? currentFolder.GetDisplayPrefs(ddlProfile.SelectedItem as User, false) ?? new DisplayPreferences {SortBy = ItemSortBy.SortName} : new DisplayPreferences {SortBy = ItemSortBy.SortName};
                                             var node = new TreeViewItem { Tag = currentFolder };
-                                            AddChildren(node, currentFolder.GetChildren(CurrentUser, prefs.IndexBy, prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref")), CurrentUser);
+
+                                            var subChildren = currentFolder.GetChildren(CurrentUser, prefs.IndexBy);
+                                            subChildren = OrderByName(subChildren, CurrentUser);
+                                            AddChildren(node, subChildren, CurrentUser);
                                             node.Header = currentFolder.Name + " (" +
                                                         node.Items.Count + ")";
                                             tvwLibrary.Items.Add(node);
@@ -100,6 +106,28 @@ namespace MediaBrowser.ServerApplication
 
         }
 
+        /// <summary>
+        /// Orders the name of the by.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>IEnumerable{BaseItem}.</returns>
+        private IEnumerable<BaseItem> OrderByName(IEnumerable<BaseItem> items, User user)
+        {
+            return OrderBy(items, user, ItemSortBy.SortName);
+        }
+
+        /// <summary>
+        /// Orders the name of the by.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>IEnumerable{BaseItem}.</returns>
+        private IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items, User user, string order)
+        {
+            return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending);
+        }
+
         /// <summary>
         /// Adds the children.
         /// </summary>
@@ -115,7 +143,7 @@ namespace MediaBrowser.ServerApplication
                 if (subFolder != null)
                 {
                     var prefs = subFolder.GetDisplayPrefs(user, false) ?? new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")};
-                    AddChildren(node, subFolder.GetChildren(user, sortBy: prefs.SortBy), user);
+                    AddChildren(node, OrderBy(subFolder.GetChildren(user), user, prefs.SortBy), user);
                     node.Header = item.Name + " (" + node.Items.Count + ")";
                 }
                 else
@@ -152,14 +180,29 @@ namespace MediaBrowser.ServerApplication
                 {
                     lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Visible;
                     ddlIndexBy.ItemsSource = folder.IndexByOptionStrings;
-                    ddlSortBy.ItemsSource = folder.SortByOptionStrings;
+
+                    ddlSortBy.ItemsSource = new []
+                        {
+                            ItemSortBy.SortName, 
+                            ItemSortBy.Album, 
+                            ItemSortBy.AlbumArtist, 
+                            ItemSortBy.Artist, 
+                            ItemSortBy.CommunityRating, 
+                            ItemSortBy.DateCreated, 
+                            ItemSortBy.DatePlayed, 
+                            ItemSortBy.PremiereDate, 
+                            ItemSortBy.ProductionYear, 
+                            ItemSortBy.Random, 
+                            ItemSortBy.Runtime
+                        };
+
                     var prefs = folder.GetDisplayPrefs(ddlProfile.SelectedItem as User, false);
                     ddlIndexBy.SelectedItem = prefs != null
                                                   ? prefs.IndexBy ?? LocalizedStrings.Instance.GetString("NoneDispPref")
                                                   : LocalizedStrings.Instance.GetString("NoneDispPref");
                     ddlSortBy.SelectedItem = prefs != null
-                                                  ? prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref")
-                                                  : LocalizedStrings.Instance.GetString("NameDispPref");
+                                                  ? prefs.SortBy ?? ItemSortBy.SortName
+                                                  : ItemSortBy.SortName;
                 }
                 else
                 {
@@ -311,7 +354,7 @@ namespace MediaBrowser.ServerApplication
                 var folder = treeItem != null
                                  ? treeItem.Tag as Folder
                                  : null;
-                var prefs = folder != null ? folder.GetDisplayPrefs(CurrentUser, true) : new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")};
+                var prefs = folder != null ? folder.GetDisplayPrefs(CurrentUser, true) : new DisplayPreferences {SortBy = ItemSortBy.SortName};
                 if (folder != null && prefs.IndexBy != ddlIndexBy.SelectedItem as string)
                 {
                     //grab UI context so we can update within the below task
@@ -326,7 +369,7 @@ namespace MediaBrowser.ServerApplication
                                                             //re-build the current item's children as an index
                                                             prefs.IndexBy = ddlIndexBy.SelectedItem as string;
                                                             treeItem.Items.Clear();
-                                                            AddChildren(treeItem,folder.GetChildren(CurrentUser, prefs.IndexBy, prefs.SortBy), CurrentUser);
+                                                            AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, prefs.IndexBy), CurrentUser, prefs.SortBy), CurrentUser);
                                                             treeItem.Header = folder.Name + "(" +
                                                                               treeItem.Items.Count + ")";
                                                             Cursor = Cursors.Arrow;
@@ -367,7 +410,7 @@ namespace MediaBrowser.ServerApplication
                                                             //re-sort
                                                             prefs.SortBy = ddlSortBy.SelectedItem as string;
                                                             treeItem.Items.Clear();
-                                                            AddChildren(treeItem,folder.GetChildren(CurrentUser,prefs.IndexBy, prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref")), CurrentUser);
+                                                            AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, prefs.IndexBy), CurrentUser, prefs.SortBy ?? ItemSortBy.SortName), CurrentUser);
                                                             treeItem.Header = folder.Name + "(" +
                                                                               treeItem.Items.Count + ")";
                                                             Cursor = Cursors.Arrow;

+ 4 - 1
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -76,7 +76,7 @@
     </ManifestKeyFile>
   </PropertyGroup>
   <PropertyGroup>
-    <GenerateManifests>true</GenerateManifests>
+    <GenerateManifests>false</GenerateManifests>
   </PropertyGroup>
   <PropertyGroup>
     <SignManifests>false</SignManifests>
@@ -112,6 +112,9 @@
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
     <Prefer32Bit>true</Prefer32Bit>
   </PropertyGroup>
+  <PropertyGroup>
+    <TargetZone>LocalIntranet</TargetZone>
+  </PropertyGroup>
   <ItemGroup>
     <Reference Include="Hardcodet.Wpf.TaskbarNotification">
       <HintPath>..\packages\Hardcodet.Wpf.TaskbarNotification.1.0.4.0\lib\net40\Hardcodet.Wpf.TaskbarNotification.dll</HintPath>

BIN
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication_TemporaryKey.pfx


+ 15 - 8
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Extensions;
+using System.Reflection;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller;
@@ -391,7 +392,6 @@ namespace MediaBrowser.WebDashboard.Api
                             {
                                 "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js", 
                                 "http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.js",
-                                "../jsapiclient.js" + versionString,
                                 "scripts/all.js" + versionString
             };
 
@@ -447,20 +447,27 @@ namespace MediaBrowser.WebDashboard.Api
 
             var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
 
+            await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false);
+
             foreach (var file in scriptFiles)
             {
-                using (var stream = assembly.GetManifestResourceStream(resourcePrefix + file))
-                {
-                    await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
-                    await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
-                }
+                await AppendResource(assembly, memoryStream, resourcePrefix + file, newLineBytes).ConfigureAwait(false);
             }
 
             memoryStream.Position = 0;
             return memoryStream;
         }
 
+        private async Task AppendResource(Assembly assembly, Stream outputStream, string path, byte[] newLineBytes)
+        {
+            using (var stream = assembly.GetManifestResourceStream(path))
+            {
+                await stream.CopyToAsync(outputStream).ConfigureAwait(false);
+
+                await outputStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
+            }
+        }
+
     }
 
 }

+ 0 - 0
MediaBrowser.Api/Javascript/ApiClient.js → MediaBrowser.WebDashboard/ApiClient.js


+ 2 - 5
MediaBrowser.WebDashboard/Html/advancedMetadata.html

@@ -21,13 +21,10 @@
                             <label>Disable internet providers for:</label>
                             <div id="divItemTypes"></div>
                         </li>
-                        <li>
-                            <button type="submit" data-theme="b">
+                        <li style="display: none;">
+                            <button class="btnSubmit" type="submit" data-theme="b">
                                 Save
                             </button>
-                            <button type="button" onclick="Dashboard.navigate('dashboard.html');">
-                                Cancel
-                            </button>
                         </li>
                     </ul>
                 </form>

+ 7 - 10
MediaBrowser.WebDashboard/Html/metadata.html

@@ -18,32 +18,29 @@
                 <form id="metadataConfigurationForm">
                     <ul data-role="listview" class="ulForm">
                         <li>
-                            <input type="checkbox" id="chkEnableInternetProviders" name="chkEnableInternetProviders" />
+                            <input type="checkbox" id="chkEnableInternetProviders" name="chkEnableInternetProviders" onchange="MetadataConfigurationPage.submit();" />
                             <label for="chkEnableInternetProviders">Download metadata from the internet </label>
                         </li>
                         <li>
-                            <input type="checkbox" id="chkSaveLocal" name="chkSaveLocal" />
+                            <input type="checkbox" id="chkSaveLocal" name="chkSaveLocal onchange="MetadataConfigurationPage.submit();" />
                             <label for="chkSaveLocal">Save metadata within media folders </label>
                         </li>
                         <li>
                             <label for="txtRefreshDays">Metadata refresh period (days): </label>
-                            <input type="number" id="txtRefreshDays" name="txtRefreshDays" pattern="[0-9]*" required="required" min="1" />
+                            <input type="number" id="txtRefreshDays" name="txtRefreshDays" pattern="[0-9]*" required="required" min="1" onchange="MetadataConfigurationPage.submit();" />
                         </li>
                         <li>
                             <label for="selectLanguage">Preferred language: </label>
-                            <select name="selectLanguage" id="selectLanguage"></select>
+                            <select name="selectLanguage" id="selectLanguage" onchange="MetadataConfigurationPage.submit();"></select>
                         </li>
                         <li>
                             <label for="selectCountry">Country: </label>
-                            <select name="selectCountry" id="selectCountry"></select>
+                            <select name="selectCountry" id="selectCountry" onchange="MetadataConfigurationPage.submit();"></select>
                         </li>
-                        <li>
-                            <button type="submit" data-theme="b">
+                        <li style="display: none;">
+                            <button class="btnSubmit" type="submit" data-theme="b">
                                 Save
                             </button>
-                            <button type="button" onclick="Dashboard.navigate('dashboard.html');">
-                                Cancel
-                            </button>
                         </li>
                     </ul>
                 </form>

+ 67 - 70
MediaBrowser.WebDashboard/Html/metadataImages.html

@@ -18,7 +18,7 @@
                 <form id="metadataImagesConfigurationForm">
                     <ul data-role="listview" class="ulForm">
                         <li>
-                            <input type="checkbox" id="chkRefreshItemImages" name="chkRefreshItemImages" />
+                            <input type="checkbox" id="chkRefreshItemImages" name="chkRefreshItemImages" onchange="MetadataImagesPage.submit();" />
                             <label for="chkRefreshItemImages">Refresh existing images </label>
                             <div class="fieldDescription">
                                 When enabled, images will be refreshed periodically
@@ -26,100 +26,100 @@
                         </li>
                         <li>
                             <label for="txtNumbackdrops">Max number of backdrops per item: </label>
-                            <input type="number" id="txtNumbackdrops" name="txtNumbackdrops" pattern="[0-9]*" required="required" min="1" />
+                            <input type="number" id="txtNumbackdrops" name="txtNumbackdrops" pattern="[0-9]*" required="required" min="1" onchange="MetadataImagesPage.submit();" />
                         </li>
                         <li>
                             <label>Enable additional image downloading:</label>
-                                
-                                <div data-role="collapsible">
-                                    <h3>Movies</h3>
-                                    <div data-role="controlgroup">
-                                        <input type="checkbox" data-mini="true" id="chkDownloadMovieArt" name="chkDownloadMovieArt" />
-                                        <label for="chkDownloadMovieArt">Movie Art</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadMovieBanner" name="chkDownloadMovieBanner" />
-                                        <label for="chkDownloadMovieBanner">Movie Banner</label>
+                            <div data-role="collapsible">
+                                <h3>Movies</h3>
+                                <div data-role="controlgroup">
+                                    <input type="checkbox" data-mini="true" id="chkDownloadMovieArt" name="chkDownloadMovieArt" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadMovieArt">Movie Art</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadMovieDisc" name="chkDownloadMovieDisc" />
-                                        <label for="chkDownloadMovieDisc">Movie Disc</label>
+                                    <input type="checkbox" data-mini="true" id="chkDownloadMovieBanner" name="chkDownloadMovieBanner" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadMovieBanner">Movie Banner</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadMovieLogo" name="chkDownloadMovieLogo" />
-                                        <label for="chkDownloadMovieLogo">Movie Logo</label>
+                                    <input type="checkbox" data-mini="true" id="chkDownloadMovieDisc" name="chkDownloadMovieDisc" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadMovieDisc">Movie Disc</label>
+
+                                    <input type="checkbox" data-mini="true" id="chkDownloadMovieLogo" name="chkDownloadMovieLogo" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadMovieLogo">Movie Logo</label>
+
+                                    <input type="checkbox" data-mini="true" id="chkDownloadMovieThumb" name="chkDownloadMovieThumb" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadMovieThumb">Movie Thumb</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadMovieThumb" name="chkDownloadMovieThumb" />
-                                        <label for="chkDownloadMovieThumb">Movie Thumb</label>
 
-                                        
-                                    </div>
                                 </div>
+                            </div>
 
-                                <div data-role="collapsible">
-                                    <h3>TV Series</h3>
-                                    <div data-role="controlgroup">
-                                        <input type="checkbox" data-mini="true" id="chKDownloadTVArt" name="chKDownloadTVArt" />
-                                        <label for="chKDownloadTVArt">TV Series Art</label>
+                            <div data-role="collapsible">
+                                <h3>TV Series</h3>
+                                <div data-role="controlgroup">
+                                    <input type="checkbox" data-mini="true" id="chKDownloadTVArt" name="chKDownloadTVArt" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chKDownloadTVArt">TV Series Art</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadTVBanner" name="chkDownloadTVBanner" />
-                                        <label for="chkDownloadTVBanner">TV Series Banner</label>
+                                    <input type="checkbox" data-mini="true" id="chkDownloadTVBanner" name="chkDownloadTVBanner" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadTVBanner">TV Series Banner</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadTVLogo" name="chkDownloadTVLogo" />
-                                        <label for="chkDownloadTVLogo">TV Series Logo</label>
+                                    <input type="checkbox" data-mini="true" id="chkDownloadTVLogo" name="chkDownloadTVLogo" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadTVLogo">TV Series Logo</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadTVThumb" name="chkDownloadTVThumb" />
-                                        <label for="chkDownloadTVThumb">TV Series Thumb</label>
-                                    </div>
+                                    <input type="checkbox" data-mini="true" id="chkDownloadTVThumb" name="chkDownloadTVThumb" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadTVThumb">TV Series Thumb</label>
                                 </div>
+                            </div>
 
 
-                                <div data-role="collapsible">
-                                    <h3>TV Seasons</h3>
-                                    <div data-role="controlgroup">
-                                        <input type="checkbox" data-mini="true" id="chkDownloadSeasonBackdrops" name="chkDownloadSeasonBackdrops" />
-                                        <label for="chkDownloadSeasonBackdrops">TV Season Backdrops</label>
-
-                                        <input type="checkbox" data-mini="true" id="chkDownloadSeasonBanner" name="chkDownloadSeasonBanner" />
-                                        <label for="chkDownloadSeasonBanner">TV Season Banner</label>
+                            <div data-role="collapsible">
+                                <h3>TV Seasons</h3>
+                                <div data-role="controlgroup">
+                                    <input type="checkbox" data-mini="true" id="chkDownloadSeasonBackdrops" name="chkDownloadSeasonBackdrops" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadSeasonBackdrops">TV Season Backdrops</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadSeasonThumb" name="chkDownloadSeasonThumb" />
-                                        <label for="chkDownloadSeasonThumb">TV Season Thumb</label>
-                                    </div>
+                                    <input type="checkbox" data-mini="true" id="chkDownloadSeasonBanner" name="chkDownloadSeasonBanner" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadSeasonBanner">TV Season Banner</label>
 
+                                    <input type="checkbox" data-mini="true" id="chkDownloadSeasonThumb" name="chkDownloadSeasonThumb" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadSeasonThumb">TV Season Thumb</label>
                                 </div>
 
-                                <div data-role="collapsible">
-                                    <h3>Music Artists</h3>
-                                    <div data-role="controlgroup">
-                                        <input type="checkbox" data-mini="true" id="chkDownloadArtistThumb" name="chkDownloadArtistThumb" />
-                                        <label for="chkDownloadArtistThumb">Music Artist Thumb (primary image)</label>
+                            </div>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadArtistBackdrops" name="chkDownloadArtistBackdrops" />
-                                        <label for="chkDownloadArtistBackdrops">Music Artist Backdrops</label>
+                            <div data-role="collapsible">
+                                <h3>Music Artists</h3>
+                                <div data-role="controlgroup">
+                                    <input type="checkbox" data-mini="true" id="chkDownloadArtistThumb" name="chkDownloadArtistThumb" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadArtistThumb">Music Artist Thumb (primary image)</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadArtistLogo" name="chkDownloadArtistLogo" />
-                                        <label for="chkDownloadArtistLogo">Music Artist Logo</label>
+                                    <input type="checkbox" data-mini="true" id="chkDownloadArtistBackdrops" name="chkDownloadArtistBackdrops" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadArtistBackdrops">Music Artist Backdrops</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadArtistBanner" name="chkDownloadArtistBanner" />
-                                        <label for="chkDownloadArtistBanner">Music Artist Banner</label>
-                                    </div>
+                                    <input type="checkbox" data-mini="true" id="chkDownloadArtistLogo" name="chkDownloadArtistLogo" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadArtistLogo">Music Artist Logo</label>
 
+                                    <input type="checkbox" data-mini="true" id="chkDownloadArtistBanner" name="chkDownloadArtistBanner" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadArtistBanner">Music Artist Banner</label>
                                 </div>
-                                
-                                <div data-role="collapsible">
-                                    <h3>Music Albums</h3>
-                                    <div data-role="controlgroup">
-                                        <input type="checkbox" data-mini="true" id="chkDownloadAlbumPrimary" name="chkDownloadAlbumPrimary" />
-                                        <label for="chkDownloadAlbumPrimary">Music Album Cover</label>
 
-                                        <input type="checkbox" data-mini="true" id="chkDownloadAlbumBackdrops" name="chkDownloadAlbumBackdrops" />
-                                        <label for="chkDownloadAlbumBackdrops">Music Album Backdrops</label>
-                                    </div>
+                            </div>
+
+                            <div data-role="collapsible">
+                                <h3>Music Albums</h3>
+                                <div data-role="controlgroup">
+                                    <input type="checkbox" data-mini="true" id="chkDownloadAlbumPrimary" name="chkDownloadAlbumPrimary" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadAlbumPrimary">Music Album Cover</label>
 
+                                    <input type="checkbox" data-mini="true" id="chkDownloadAlbumBackdrops" name="chkDownloadAlbumBackdrops" onchange="MetadataImagesPage.submit();" />
+                                    <label for="chkDownloadAlbumBackdrops">Music Album Backdrops</label>
                                 </div>
 
+                            </div>
+
                         </li>
                         <li>
                             <label for="selectTmdbPosterDownloadSize">Tmdb poster download size: </label>
-                            <select id="selectTmdbPosterDownloadSize" name="selectTmdbPosterDownloadSize">
+                            <select id="selectTmdbPosterDownloadSize" name="selectTmdbPosterDownloadSize" onchange="MetadataImagesPage.submit();">
                                 <option value="original">original</option>
                                 <option value="w92">w92</option>
                                 <option value="w154">w154</option>
@@ -130,7 +130,7 @@
                         </li>
                         <li>
                             <label for="selectTmdbBackdropDownloadSize">Tmdb backdrop download size: </label>
-                            <select id="selectTmdbBackdropDownloadSize" name="selectTmdbBackdropDownloadSize">
+                            <select id="selectTmdbBackdropDownloadSize" name="selectTmdbBackdropDownloadSize" onchange="MetadataImagesPage.submit();">
                                 <option value="original">original</option>
                                 <option value="w380">w380</option>
                                 <option value="w780">w780</option>
@@ -139,20 +139,17 @@
                         </li>
                         <li>
                             <label for="selectTmdbPersonImageDownloadSize">Tmdb person image download size: </label>
-                            <select id="selectTmdbPersonImageDownloadSize" name="selectTmdbPersonImageDownloadSize">
+                            <select id="selectTmdbPersonImageDownloadSize" name="selectTmdbPersonImageDownloadSize" onchange="MetadataImagesPage.submit();">
                                 <option value="original">original</option>
                                 <option value="w45">w45</option>
                                 <option value="w185">w185</option>
                                 <option value="h632">h632</option>
                             </select>
                         </li>
-                        <li>
-                            <button type="submit" data-theme="b">
+                        <li style="display: none;">
+                            <button class="btnSubmit" type="submit" data-theme="b">
                                 Save
                             </button>
-                            <button type="button" onclick="Dashboard.navigate('dashboard.html');">
-                                Cancel
-                            </button>
                         </li>
                     </ul>
                 </form>

+ 8 - 2
MediaBrowser.WebDashboard/Html/scripts/AdvancedMetadataConfigurationPage.js

@@ -33,7 +33,7 @@
 
             var checkedAttribute = configuration.InternetProviderExcludeTypes.indexOf(type) != -1 ? ' checked="checked"' : '';
 
-            html += '<input' + checkedAttribute + ' class="chkItemType" data-itemtype="' + type + '" type="checkbox" name="' + id + '" id="' + id + '" />';
+            html += '<input' + checkedAttribute + ' class="chkItemType" data-itemtype="' + type + '" type="checkbox" name="' + id + '" id="' + id + '" onchange="AdvancedMetadataConfigurationPage.submit();" />';
             html += '<label for="' + id + '">' + type + '</label>';
         }
 
@@ -42,6 +42,12 @@
         $('#divItemTypes', page).html(html).trigger("create");
     },
 
+    submit: function () {
+
+        $('.btnSubmit', $.mobile.activePage)[0].click();
+
+    },
+
     onSubmit: function () {
         Dashboard.showLoadingMsg();
 
@@ -54,7 +60,7 @@
                 return currentCheckbox.getAttribute('data-itemtype');
             });
 
-            ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult);
+            ApiClient.updateServerConfiguration(config);
         });
 
         // Disable default form submission

+ 7 - 1
MediaBrowser.WebDashboard/Html/scripts/MetadataConfigurationPage.js

@@ -79,6 +79,12 @@
         $('#selectLanguage', '#metadataConfigurationPage').html(html).selectmenu("refresh");
     },
 
+    submit: function () {
+
+        $('.btnSubmit', $.mobile.activePage)[0].click();
+
+    },
+
     onSubmit: function () {
         Dashboard.showLoadingMsg();
 
@@ -92,7 +98,7 @@
             config.PreferredMetadataLanguage = $('#selectLanguage', form).val();
             config.MetadataCountryCode = $('#selectCountry', form).val();
 
-            ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult);
+            ApiClient.updateServerConfiguration(config);
         });
 
         // Disable default form submission

+ 7 - 1
MediaBrowser.WebDashboard/Html/scripts/MetadataImagesPage.js

@@ -42,6 +42,12 @@
         Dashboard.hideLoadingMsg();
     },
 
+    submit: function () {
+
+        $('.btnSubmit', $.mobile.activePage)[0].click();
+
+    },
+
     onSubmit: function () {
         Dashboard.showLoadingMsg();
 
@@ -75,7 +81,7 @@
             config.DownloadMusicAlbumImages.Primary = $('#chkDownloadAlbumPrimary', form).checked();
             config.DownloadMusicAlbumImages.Backdrops = $('#chkDownloadAlbumBackdrops', form).checked();
 
-            ApiClient.updateServerConfiguration(config).done(Dashboard.processServerConfigurationUpdateResult);
+            ApiClient.updateServerConfiguration(config);
         });
 
         // Disable default form submission

+ 1 - 0
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -403,6 +403,7 @@
     <EmbeddedResource Include="Html\css\images\mblogotextblack.png" />
     <EmbeddedResource Include="Html\css\images\mblogotextwhite.png" />
     <EmbeddedResource Include="Html\css\images\clients\dlna.png" />
+    <EmbeddedResource Include="ApiClient.js" />
     <Content Include="Html\css\images\stars.png" />
     <EmbeddedResource Include="Html\scripts\MediaPlayer.js" />
   </ItemGroup>

+ 1 - 0
MediaBrowser.WebDashboard/packages.config

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.40" targetFramework="net45" />
   <package id="ServiceStack" version="3.9.38" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.38" targetFramework="net45" />
   <package id="ServiceStack.OrmLite.SqlServer" version="3.9.39" targetFramework="net45" />

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.40</version>
+        <version>3.0.43</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.40" />
+            <dependency id="MediaBrowser.Common" version="3.0.43" />
             <dependency id="NLog" version="2.0.0.2000" />
             <dependency id="ServiceStack.Text" version="3.9.38" />
             <dependency id="protobuf-net" version="2.0.0.621" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.40</version>
+        <version>3.0.43</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

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

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