浏览代码

Merge pull request #1 from MediaBrowser/master

Sync
7illusions 11 年之前
父节点
当前提交
c25610e0d5
共有 40 个文件被更改,包括 798 次插入80 次删除
  1. 3 2
      MediaBrowser.Api/MediaBrowser.Api.csproj
  2. 80 0
      MediaBrowser.Api/Movies/CollectionService.cs
  3. 1 1
      MediaBrowser.Api/Movies/MoviesService.cs
  4. 1 1
      MediaBrowser.Api/Movies/TrailersService.cs
  5. 5 1
      MediaBrowser.Api/SearchService.cs
  6. 12 3
      MediaBrowser.Controller/Collections/CollectionCreationOptions.cs
  7. 5 4
      MediaBrowser.Controller/Collections/ICollectionManager.cs
  8. 9 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  9. 1 14
      MediaBrowser.Controller/Entities/BasePluginFolder.cs
  10. 52 18
      MediaBrowser.Controller/Entities/Folder.cs
  11. 7 3
      MediaBrowser.Controller/Entities/LinkedChild.cs
  12. 1 2
      MediaBrowser.Controller/Library/IUserManager.cs
  13. 63 0
      MediaBrowser.Model/ApiClient/IApiClient.cs
  14. 3 0
      MediaBrowser.Model/Querying/ItemSortBy.cs
  15. 4 0
      MediaBrowser.Model/Search/SearchQuery.cs
  16. 10 0
      MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
  17. 129 0
      MediaBrowser.Providers/BoxSets/BoxSetXmlParser.cs
  18. 1 1
      MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs
  19. 1 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  20. 1 0
      MediaBrowser.Providers/Photos/PhotoProvider.cs
  21. 38 1
      MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
  22. 123 7
      MediaBrowser.Server.Implementations/Collections/CollectionManager.cs
  23. 55 0
      MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs
  24. 6 0
      MediaBrowser.Server.Implementations/Library/SearchEngine.cs
  25. 2 7
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  26. 2 1
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  27. 12 5
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  28. 54 0
      MediaBrowser.Server.Implementations/Sorting/GameSystemComparer.cs
  29. 46 0
      MediaBrowser.Server.Implementations/Sorting/PlayersComparer.cs
  30. 30 0
      MediaBrowser.Server.Implementations/Sorting/StudioComparer.cs
  31. 1 1
      MediaBrowser.Server.Implementations/packages.config
  32. 15 1
      MediaBrowser.ServerApplication/App.config
  33. 5 0
      MediaBrowser.ServerApplication/ApplicationHost.cs
  34. 6 0
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  35. 1 1
      MediaBrowser.Tests/app.config
  36. 1 0
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  37. 7 1
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  38. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  39. 1 1
      Nuget/MediaBrowser.Common.nuspec
  40. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

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

@@ -66,6 +66,7 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
+    <Compile Include="Movies\CollectionService.cs" />
     <Compile Include="Music\AlbumsService.cs" />
     <Compile Include="AppThemeService.cs" />
     <Compile Include="BaseApiService.cs" />
@@ -91,7 +92,7 @@
     <Compile Include="Library\LibraryStructureService.cs" />
     <Compile Include="LiveTv\LiveTvService.cs" />
     <Compile Include="LocalizationService.cs" />
-    <Compile Include="MoviesService.cs" />
+    <Compile Include="Movies\MoviesService.cs" />
     <Compile Include="NewsService.cs" />
     <Compile Include="NotificationsService.cs" />
     <Compile Include="PackageReviewService.cs" />
@@ -118,7 +119,7 @@
     <Compile Include="SessionsService.cs" />
     <Compile Include="SimilarItemsHelper.cs" />
     <Compile Include="SystemService.cs" />
-    <Compile Include="TrailersService.cs" />
+    <Compile Include="Movies\TrailersService.cs" />
     <Compile Include="TvShowsService.cs" />
     <Compile Include="UserLibrary\ArtistsService.cs" />
     <Compile Include="UserLibrary\BaseItemsByNameService.cs" />

+ 80 - 0
MediaBrowser.Api/Movies/CollectionService.cs

@@ -0,0 +1,80 @@
+using MediaBrowser.Controller.Collections;
+using ServiceStack;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Movies
+{
+    [Route("/Collections", "POST")]
+    [Api(Description = "Creates a new collection")]
+    public class CreateCollection : IReturnVoid
+    {
+        [ApiMember(Name = "IsLocked", Description = "Whether or not to lock the new collection.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
+        public bool IsLocked { get; set; }
+
+        [ApiMember(Name = "Name", Description = "The name of the new collection.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string Name { get; set; }
+
+        [ApiMember(Name = "ParentId", Description = "Optional - create the collection within a specific folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public Guid? ParentId { get; set; }
+    }
+
+    [Route("/Collections/{Id}/Items", "POST")]
+    [Api(Description = "Adds items to a collection")]
+    public class AddToCollection : IReturnVoid
+    {
+        [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string Ids { get; set; }
+
+        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public Guid Id { get; set; }
+    }
+
+    [Route("/Collections/{Id}/Items", "DELETE")]
+    [Api(Description = "Removes items from a collection")]
+    public class RemoveFromCollection : IReturnVoid
+    {
+        [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string Ids { get; set; }
+
+        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
+        public Guid Id { get; set; }
+    }
+
+    public class CollectionService : BaseApiService
+    {
+        private readonly ICollectionManager _collectionManager;
+
+        public CollectionService(ICollectionManager collectionManager)
+        {
+            _collectionManager = collectionManager;
+        }
+
+        public void Post(CreateCollection request)
+        {
+            var task = _collectionManager.CreateCollection(new CollectionCreationOptions
+            {
+                IsLocked = request.IsLocked,
+                Name = request.Name,
+                ParentId = request.ParentId
+            });
+
+            Task.WaitAll(task);
+        }
+
+        public void Post(AddToCollection request)
+        {
+            var task = _collectionManager.AddToCollection(request.Id, request.Ids.Split(',').Select(i => new Guid(i)));
+
+            Task.WaitAll(task);
+        }
+
+        public void Delete(RemoveFromCollection request)
+        {
+            var task = _collectionManager.RemoveFromCollection(request.Id, request.Ids.Split(',').Select(i => new Guid(i)));
+
+            Task.WaitAll(task);
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Api/MoviesService.cs → MediaBrowser.Api/Movies/MoviesService.cs

@@ -5,7 +5,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using ServiceStack;
 
-namespace MediaBrowser.Api
+namespace MediaBrowser.Api.Movies
 {
     /// <summary>
     /// Class GetSimilarMovies

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

@@ -5,7 +5,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using ServiceStack;
 
-namespace MediaBrowser.Api
+namespace MediaBrowser.Api.Movies
 {
     /// <summary>
     /// Class GetSimilarTrailers

+ 5 - 1
MediaBrowser.Api/SearchService.cs

@@ -63,6 +63,9 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "IncludeArtists", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool IncludeArtists { get; set; }
 
+        [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string IncludeItemTypes { get; set; }
+        
         public GetSearchHints()
         {
             IncludeArtists = true;
@@ -130,7 +133,8 @@ namespace MediaBrowser.Api
                 IncludePeople = request.IncludePeople,
                 IncludeStudios = request.IncludeStudios,
                 StartIndex = request.StartIndex,
-                UserId = request.UserId
+                UserId = request.UserId,
+                IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray()
 
             }).ConfigureAwait(false);
 

+ 12 - 3
MediaBrowser.Controller/Collections/CollectionCreationOptions.cs

@@ -1,13 +1,22 @@
-using System;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Collections
 {
-    public class CollectionCreationOptions
+    public class CollectionCreationOptions : IHasProviderIds
     {
         public string Name { get; set; }
 
-        public Guid ParentId { get; set; }
+        public Guid? ParentId { get; set; }
 
         public bool IsLocked { get; set; }
+
+        public Dictionary<string, string> ProviderIds { get; set; }
+
+        public CollectionCreationOptions()
+        {
+            ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+        }
     }
 }

+ 5 - 4
MediaBrowser.Controller/Collections/ICollectionManager.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Collections
@@ -16,16 +17,16 @@ namespace MediaBrowser.Controller.Collections
         /// Adds to collection.
         /// </summary>
         /// <param name="collectionId">The collection identifier.</param>
-        /// <param name="itemId">The item identifier.</param>
+        /// <param name="itemIds">The item ids.</param>
         /// <returns>Task.</returns>
-        Task AddToCollection(Guid collectionId, Guid itemId);
+        Task AddToCollection(Guid collectionId, IEnumerable<Guid> itemIds);
 
         /// <summary>
         /// Removes from collection.
         /// </summary>
         /// <param name="collectionId">The collection identifier.</param>
-        /// <param name="itemId">The item identifier.</param>
+        /// <param name="itemIds">The item ids.</param>
         /// <returns>Task.</returns>
-        Task RemoveFromCollection(Guid collectionId, Guid itemId);
+        Task RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds);
     }
 }

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

@@ -124,6 +124,15 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        [IgnoreDataMember]
+        public virtual bool IsHidden
+        {
+            get
+            {
+                return false;
+            }
+        }
+
         [IgnoreDataMember]
         public virtual bool IsOwnedItem
         {

+ 1 - 14
MediaBrowser.Controller/Entities/BasePluginFolder.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Entities;
-
+
 namespace MediaBrowser.Controller.Entities
 {
     /// <summary>
@@ -8,18 +7,6 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public abstract class BasePluginFolder : Folder, ICollectionFolder, IByReferenceItem
     {
-        /// <summary>
-        /// Gets or sets the type of the location.
-        /// </summary>
-        /// <value>The type of the location.</value>
-        public override LocationType LocationType
-        {
-            get
-            {
-                return LocationType.Virtual;
-            }
-        }
-
         protected BasePluginFolder()
         {
             DisplayMediaType = "CollectionFolder";

+ 52 - 18
MediaBrowser.Controller/Entities/Folder.cs

@@ -264,7 +264,7 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         public IEnumerable<BaseItem> Children
         {
-            get { return ActualChildren; }
+            get { return ActualChildren.Where(i => !i.IsHidden); }
         }
 
         /// <summary>
@@ -745,9 +745,9 @@ namespace MediaBrowser.Controller.Entities
 
             var list = new List<BaseItem>();
 
-            AddChildrenToList(user, includeLinkedChildren, list, false, null);
+            var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false, null);
 
-            return list;
+            return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list;
         }
 
         /// <summary>
@@ -905,13 +905,6 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>BaseItem.</returns>
         private BaseItem GetLinkedChild(LinkedChild info)
         {
-            if (string.IsNullOrEmpty(info.Path))
-            {
-                throw new ArgumentException("Encountered linked child with empty path.");
-            }
-
-            BaseItem item = null;
-
             // First get using the cached Id
             if (info.ItemId.HasValue)
             {
@@ -920,20 +913,19 @@ namespace MediaBrowser.Controller.Entities
                     return null;
                 }
 
-                item = LibraryManager.GetItemById(info.ItemId.Value);
-            }
+                var itemById = LibraryManager.GetItemById(info.ItemId.Value);
 
-            // If still null, search by path
-            if (item == null)
-            {
-                item = LibraryManager.RootFolder.FindByPath(info.Path);
+                if (itemById != null)
+                {
+                    return itemById;
+                }
             }
 
+            var item = FindLinkedChild(info);
+
             // If still null, log
             if (item == null)
             {
-                Logger.Warn("Unable to find linked item at {0}", info.Path);
-
                 // Don't keep searching over and over
                 info.ItemId = Guid.Empty;
             }
@@ -946,6 +938,43 @@ namespace MediaBrowser.Controller.Entities
             return item;
         }
 
+        private BaseItem FindLinkedChild(LinkedChild info)
+        {
+            if (!string.IsNullOrEmpty(info.Path))
+            {
+                var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path);
+
+                if (itemByPath == null)
+                {
+                    Logger.Warn("Unable to find linked item at path {0}", info.Path);
+                }
+
+                return itemByPath;
+            }
+
+            if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType))
+            {
+                return LibraryManager.RootFolder.RecursiveChildren.FirstOrDefault(i =>
+                {
+                    if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase))
+                    {
+                        if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase))
+                        {
+                            if (info.ItemYear.HasValue)
+                            {
+                                return info.ItemYear.Value == (i.ProductionYear ?? -1);
+                            }
+                            return true;
+                        }
+                    }
+
+                    return false;
+                });
+            }
+
+            return null;
+        }
+
         protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
             var changesFound = false;
@@ -1106,5 +1135,10 @@ namespace MediaBrowser.Controller.Entities
             return GetRecursiveChildren(user).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual)
                 .All(i => i.IsUnplayed(user));
         }
+
+        public IEnumerable<BaseItem> GetHiddenChildren()
+        {
+            return ActualChildren.Where(i => i.IsHidden);
+        }
     }
 }

+ 7 - 3
MediaBrowser.Controller/Entities/LinkedChild.cs

@@ -9,6 +9,10 @@ namespace MediaBrowser.Controller.Entities
         public string Path { get; set; }
         public LinkedChildType Type { get; set; }
 
+        public string ItemName { get; set; }
+        public string ItemType { get; set; }
+        public int? ItemYear { get; set; }
+
         /// <summary>
         /// Serves as a cache
         /// </summary>
@@ -18,8 +22,8 @@ namespace MediaBrowser.Controller.Entities
 
     public enum LinkedChildType
     {
-        Manual = 1,
-        Shortcut = 2
+        Manual = 0,
+        Shortcut = 1
     }
 
     public class LinkedChildComparer : IEqualityComparer<LinkedChild>
@@ -35,7 +39,7 @@ namespace MediaBrowser.Controller.Entities
 
         public int GetHashCode(LinkedChild obj)
         {
-            return (obj.Path + obj.Type.ToString()).GetHashCode();
+            return (obj.Path + obj.Type).GetHashCode();
         }
     }
 }

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

@@ -51,9 +51,8 @@ namespace MediaBrowser.Controller.Library
         /// Refreshes metadata for each user
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
         /// <returns>Task.</returns>
-        Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false);
+        Task RefreshUsersMetadata(CancellationToken cancellationToken);
 
         /// <summary>
         /// Renames the user.

+ 63 - 0
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -1016,6 +1016,15 @@ namespace MediaBrowser.Model.ApiClient
         /// <returns>Task{QueryResult{ProgramInfoDto}}.</returns>
         Task<QueryResult<ProgramInfoDto>> GetLiveTvProgramsAsync(ProgramQuery query, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Gets the live tv program asynchronous.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="userId">The user identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{ProgramInfoDto}.</returns>
+        Task<ProgramInfoDto> GetLiveTvProgramAsync(string id, string userId, CancellationToken cancellationToken);
+
         /// <summary>
         /// Gets the recommended live tv programs asynchronous.
         /// </summary>
@@ -1023,6 +1032,38 @@ namespace MediaBrowser.Model.ApiClient
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{QueryResult{ProgramInfoDto}}.</returns>
         Task<QueryResult<ProgramInfoDto>> GetRecommendedLiveTvProgramsAsync(RecommendedProgramQuery query, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Creates the live tv timer asynchronous.
+        /// </summary>
+        /// <param name="timer">The timer.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task CreateLiveTvTimerAsync(TimerInfoDto timer, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Updates the live tv timer asynchronous.
+        /// </summary>
+        /// <param name="timer">The timer.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateLiveTvTimerAsync(TimerInfoDto timer, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Creates the live tv series timer asynchronous.
+        /// </summary>
+        /// <param name="timer">The timer.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task CreateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Updates the live tv series timer asynchronous.
+        /// </summary>
+        /// <param name="timer">The timer.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
         
         /// <summary>
         /// Gets the live tv timer asynchronous.
@@ -1071,5 +1112,27 @@ namespace MediaBrowser.Model.ApiClient
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task DeleteLiveTvRecordingAsync(string id, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the default timer information.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{SeriesTimerInfoDto}.</returns>
+        Task<SeriesTimerInfoDto> GetDefaultLiveTvTimerInfo(CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the live tv guide information.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{GuideInfo}.</returns>
+        Task<GuideInfo> GetLiveTvGuideInfo(CancellationToken cancellationToken);
+        
+        /// <summary>
+        /// Gets the default timer information.
+        /// </summary>
+        /// <param name="programId">The program identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{SeriesTimerInfoDto}.</returns>
+        Task<SeriesTimerInfoDto> GetDefaultLiveTvTimerInfo(string programId, CancellationToken cancellationToken);
     }
 }

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

@@ -86,5 +86,8 @@ namespace MediaBrowser.Model.Querying
         public const string VideoBitRate = "VideoBitRate";
         public const string AirTime = "AirTime";
         public const string Metascore = "Metascore";
+        public const string Studio = "Studio";
+        public const string Players = "Players";
+        public const string GameSystem = "GameSystem";
     }
 }

+ 4 - 0
MediaBrowser.Model/Search/SearchQuery.cs

@@ -33,6 +33,8 @@ namespace MediaBrowser.Model.Search
         public bool IncludeStudios { get; set; }
         public bool IncludeArtists { get; set; }
 
+        public string[] IncludeItemTypes { get; set; }
+
         public SearchQuery()
         {
             IncludeArtists = true;
@@ -40,6 +42,8 @@ namespace MediaBrowser.Model.Search
             IncludeMedia = true;
             IncludePeople = true;
             IncludeStudios = true;
+
+            IncludeItemTypes = new string[] { };
         }
     }
 }

+ 10 - 0
MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
@@ -37,6 +38,15 @@ namespace MediaBrowser.Providers.BoxSets
         protected override void MergeData(BoxSet source, BoxSet target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+
+            if (mergeMetadataSettings)
+            {
+                var list = source.LinkedChildren.ToList();
+
+                list.AddRange(target.LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut));
+
+                target.LinkedChildren = list;
+            }
         }
 
         protected override ItemUpdateType BeforeSave(BoxSet item)

+ 129 - 0
MediaBrowser.Providers/BoxSets/BoxSetXmlParser.cs

@@ -0,0 +1,129 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Xml;
+
+namespace MediaBrowser.Providers.BoxSets
+{
+    public class BoxSetXmlParser : BaseItemXmlParser<BoxSet>
+    {
+        private readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+        public BoxSetXmlParser(ILogger logger)
+            : base(logger)
+        {
+        }
+
+        protected override void FetchDataFromXmlNode(XmlReader reader, BoxSet item)
+        {
+            switch (reader.Name)
+            {
+                case "CollectionItems":
+
+                    using (var subReader = reader.ReadSubtree())
+                    {
+                        FetchFromCollectionItemsNode(subReader, item);
+                    }
+                    break;
+
+                default:
+                    base.FetchDataFromXmlNode(reader, item);
+                    break;
+            }
+        }
+
+        private void FetchFromCollectionItemsNode(XmlReader reader, BoxSet item)
+        {
+            reader.MoveToContent();
+
+            var list = new List<LinkedChild>();
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "CollectionItem":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    var child = GetLinkedChild(subReader);
+
+                                    if (child != null)
+                                    {
+                                        list.Add(child);
+                                    }
+                                }
+
+                                break;
+                            }
+
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+
+            item.LinkedChildren = list;
+        }
+
+        private LinkedChild GetLinkedChild(XmlReader reader)
+        {
+            reader.MoveToContent();
+
+            var linkedItem = new LinkedChild
+            {
+                Type = LinkedChildType.Manual
+            };
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "Name":
+                            {
+                                linkedItem.ItemName = reader.ReadElementContentAsString();
+                                break;
+                            }
+
+                        case "Type":
+                            {
+                                linkedItem.ItemType = reader.ReadElementContentAsString();
+                                break;
+                            }
+
+                        case "Year":
+                            {
+                                var val = reader.ReadElementContentAsString();
+
+                                if (!string.IsNullOrWhiteSpace(val))
+                                {
+                                    int rval;
+
+                                    if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval))
+                                    {
+                                        linkedItem.ItemYear = rval;
+                                    }
+                                }
+
+                                break;
+                            }
+
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+
+            return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem;
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs

@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.BoxSets
 
         protected override void Fetch(LocalMetadataResult<BoxSet> result, string path, CancellationToken cancellationToken)
         {
-            new BaseItemXmlParser<BoxSet>(_logger).Fetch(result.Item, path, cancellationToken);
+            new BoxSetXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
         }
 
         protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

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

@@ -75,6 +75,7 @@
     <Compile Include="All\LocalImageProvider.cs" />
     <Compile Include="Books\BookMetadataService.cs" />
     <Compile Include="BoxSets\BoxSetMetadataService.cs" />
+    <Compile Include="BoxSets\BoxSetXmlParser.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
     <Compile Include="Folders\CollectionFolderImageProvider.cs" />

+ 1 - 0
MediaBrowser.Providers/Photos/PhotoProvider.cs

@@ -25,6 +25,7 @@ namespace MediaBrowser.Providers.Photos
         public Task<ItemUpdateType> FetchAsync(Photo item, IDirectoryService directoryService, CancellationToken cancellationToken)
         {
             item.SetImagePath(ImageType.Primary, item.Path);
+            item.SetImagePath(ImageType.Backdrop, item.Path);
 
             if (item.Path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || item.Path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
             {

+ 38 - 1
MediaBrowser.Providers/Savers/XmlSaverHelpers.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
@@ -82,7 +83,8 @@ namespace MediaBrowser.Providers.Savers
                     "TVRageId",
                     "VoteCount",
                     "Website",
-                    "Zap2ItId"
+                    "Zap2ItId",
+                    "CollectionItems"
 
         }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 
@@ -580,6 +582,12 @@ namespace MediaBrowser.Providers.Savers
 
                 builder.Append("</Persons>");
             }
+
+            var folder = item as BoxSet;
+            if (folder != null)
+            {
+                AddCollectionItems(folder, builder);
+            }
         }
 
         public static void AddChapters(Video item, StringBuilder builder, IItemRepository repository)
@@ -631,5 +639,34 @@ namespace MediaBrowser.Providers.Savers
                 }
             }
         }
+
+        public static void AddCollectionItems(Folder item, StringBuilder builder)
+        {
+            var items = item.LinkedChildren
+                .Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName))
+                .ToList();
+
+            if (items.Count == 0)
+            {
+                return;
+            }
+
+            builder.Append("<CollectionItems>");
+            foreach (var link in items)
+            {
+                builder.Append("<CollectionItem>");
+
+                builder.Append("<Name>" + SecurityElement.Escape(link.ItemName) + "</Name>");
+                builder.Append("<Type>" + SecurityElement.Escape(link.ItemType) + "</Type>");
+
+                if (link.ItemYear.HasValue)
+                {
+                    builder.Append("<Year>" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + "</Year>");
+                }
+
+                builder.Append("</CollectionItem>");
+            }
+            builder.Append("</CollectionItems>");
+        }
     }
 }

+ 123 - 7
MediaBrowser.Server.Implementations/Collections/CollectionManager.cs

@@ -5,7 +5,9 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using System;
+using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -28,9 +30,12 @@ namespace MediaBrowser.Server.Implementations.Collections
         {
             var name = options.Name;
 
-            var folderName = _fileSystem.GetValidFilename(name);
+            // Need to use the [boxset] suffix
+            // If internet metadata is not found, or if xml saving is off there will be no collection.xml
+            // This could cause it to get re-resolved as a plain folder
+            var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
 
-            var parentFolder = _libraryManager.GetItemById(options.ParentId) as Folder;
+            var parentFolder = GetParentFolder(options.ParentId);
 
             if (parentFolder == null)
             {
@@ -51,7 +56,8 @@ namespace MediaBrowser.Server.Implementations.Collections
                     Parent = parentFolder,
                     DisplayMediaType = "Collection",
                     Path = path,
-                    DontFetchMeta = options.IsLocked
+                    DontFetchMeta = options.IsLocked,
+                    ProviderIds = options.ProviderIds
                 };
 
                 await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false);
@@ -66,14 +72,124 @@ namespace MediaBrowser.Server.Implementations.Collections
             }
         }
 
-        public Task AddToCollection(Guid collectionId, Guid itemId)
+        private Folder GetParentFolder(Guid? parentId)
         {
-            throw new NotImplementedException();
+            if (parentId.HasValue)
+            {
+                if (parentId.Value == Guid.Empty)
+                {
+                    throw new ArgumentNullException("parentId");
+                }
+
+                return _libraryManager.GetItemById(parentId.Value) as Folder;
+            }
+
+            return _libraryManager.RootFolder.Children.OfType<ManualCollectionsFolder>().FirstOrDefault() ??
+                _libraryManager.RootFolder.GetHiddenChildren().OfType<ManualCollectionsFolder>().FirstOrDefault();
+        }
+
+        public async Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
+        {
+            var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
+
+            if (collection == null)
+            {
+                throw new ArgumentException("No collection exists with the supplied Id");
+            }
+
+            var list = new List<LinkedChild>();
+
+            foreach (var itemId in ids)
+            {
+                var item = _libraryManager.GetItemById(itemId);
+
+                if (item == null)
+                {
+                    throw new ArgumentException("No item exists with the supplied Id");
+                }
+
+                if (collection.LinkedChildren.Any(i => i.ItemId.HasValue && i.ItemId == itemId))
+                {
+                    throw new ArgumentException("Item already exists in collection");
+                }
+
+                list.Add(new LinkedChild
+                {
+                    ItemName = item.Name,
+                    ItemYear = item.ProductionYear,
+                    ItemType = item.GetType().Name,
+                    Type = LinkedChildType.Manual
+                });
+            }
+
+            collection.LinkedChildren.AddRange(list);
+
+            await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
+
+            await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false);
         }
 
-        public Task RemoveFromCollection(Guid collectionId, Guid itemId)
+        public async Task RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
         {
-            throw new NotImplementedException();
+            var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
+
+            if (collection == null)
+            {
+                throw new ArgumentException("No collection exists with the supplied Id");
+            }
+
+            var list = new List<LinkedChild>();
+
+            foreach (var itemId in itemIds)
+            {
+                var child = collection.LinkedChildren.FirstOrDefault(i => i.ItemId.HasValue && i.ItemId.Value == itemId);
+
+                if (child == null)
+                {
+                    throw new ArgumentException("No collection title exists with the supplied Id");
+                }
+
+                list.Add(child);
+            }
+
+            var shortcutFiles = Directory
+                .EnumerateFiles(collection.Path, "*", SearchOption.TopDirectoryOnly)
+                .Where(i => _fileSystem.IsShortcut(i))
+                .ToList();
+
+            var shortcutFilesToDelete = list.Where(child => !string.IsNullOrWhiteSpace(child.Path) && child.Type == LinkedChildType.Shortcut)
+                .Select(child => shortcutFiles.FirstOrDefault(i => string.Equals(child.Path, _fileSystem.ResolveShortcut(i), StringComparison.OrdinalIgnoreCase)))
+                .Where(i => !string.IsNullOrWhiteSpace(i))
+                .ToList();
+
+            foreach (var file in shortcutFilesToDelete)
+            {
+                _iLibraryMonitor.ReportFileSystemChangeBeginning(file);
+            }
+
+            try
+            {
+                foreach (var file in shortcutFilesToDelete)
+                {
+                    File.Delete(file);
+                }
+                
+                foreach (var child in list)
+                {
+                    collection.LinkedChildren.Remove(child);
+                }
+            }
+            finally
+            {
+                foreach (var file in shortcutFilesToDelete)
+                {
+                    _iLibraryMonitor.ReportFileSystemChangeComplete(file, false);
+                }
+            }
+
+            await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
+
+            await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false);
         }
     }
 }

+ 55 - 0
MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs

@@ -0,0 +1,55 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.Collections
+{
+    public class CollectionsDynamicFolder : IVirtualFolderCreator
+    {
+        private readonly IApplicationPaths _appPaths;
+
+        public CollectionsDynamicFolder(IApplicationPaths appPaths)
+        {
+            _appPaths = appPaths;
+        }
+
+        public BasePluginFolder GetFolder()
+        {
+            var path = Path.Combine(_appPaths.DataPath, "collections");
+
+            Directory.CreateDirectory(path);
+
+            return new ManualCollectionsFolder
+            {
+                Path = path
+            };
+        }
+    }
+
+    public class ManualCollectionsFolder : BasePluginFolder
+    {
+        public ManualCollectionsFolder()
+        {
+            Name = "Collections";
+        }
+
+        public override bool IsVisible(User user)
+        {
+            if (!GetChildren(user, true).Any())
+            {
+                return false;
+            }
+
+            return base.IsVisible(user);
+        }
+
+        public override bool IsHidden
+        {
+            get
+            {
+                return !ActualChildren.Any() || base.IsHidden;
+            }
+        }
+    }
+}

+ 6 - 0
MediaBrowser.Server.Implementations/Library/SearchEngine.cs

@@ -37,6 +37,12 @@ namespace MediaBrowser.Server.Implementations.Library
 
             var results = await GetSearchHints(inputItems, query).ConfigureAwait(false);
 
+            // Include item types
+            if (query.IncludeItemTypes.Length > 0)
+            {
+                results = results.Where(f => query.IncludeItemTypes.Contains(f.Item.GetType().Name, StringComparer.OrdinalIgnoreCase));
+            }
+
             var searchResultArray = results.ToArray();
             results = searchResultArray;
 

+ 2 - 7
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -189,15 +189,10 @@ namespace MediaBrowser.Server.Implementations.Library
         /// Refreshes metadata for each user
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
         /// <returns>Task.</returns>
-        public Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false)
+        public Task RefreshUsersMetadata(CancellationToken cancellationToken)
         {
-            var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions
-            {
-                ReplaceAllMetadata = force
-
-            }, cancellationToken)).ToList();
+            var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions(), cancellationToken)).ToList();
 
             return Task.WhenAll(tasks);
         }

+ 2 - 1
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -582,7 +582,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 .Select(i => _libraryManager.GetGenre(i))
                 .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
 
-            programs = programList.OrderByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres))
+            programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1)
+                .ThenByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres))
                 .ThenBy(i => i.StartDate);
 
             if (query.Limit.HasValue)

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

@@ -65,12 +65,9 @@
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
-    <Reference Include="System.Data.SQLite, Version=1.0.90.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86" Condition=" '$(ConfigurationName)' != 'Release Mono' ">
+    <Reference Include="System.Data.SQLite, Version=1.0.91.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.dll</HintPath>
-    </Reference>
-    <Reference Include="System.Data.SQLite.Linq" Condition=" '$(ConfigurationName)' != 'Release Mono' ">
-      <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
+      <HintPath>..\packages\System.Data.SQLite.Core.1.0.91.3\lib\net45\System.Data.SQLite.dll</HintPath>
     </Reference>
     <Reference Include="System.Drawing" />
     <Reference Include="Microsoft.CSharp" />
@@ -110,6 +107,7 @@
     </Compile>
     <Compile Include="BdInfo\BdInfoExaminer.cs" />
     <Compile Include="Collections\CollectionManager.cs" />
+    <Compile Include="Collections\CollectionsDynamicFolder.cs" />
     <Compile Include="Configuration\ServerConfigurationManager.cs" />
     <Compile Include="Drawing\ImageHeader.cs" />
     <Compile Include="Drawing\PercentPlayedDrawer.cs" />
@@ -227,6 +225,7 @@
     <Compile Include="Sorting\DateCreatedComparer.cs" />
     <Compile Include="Sorting\DatePlayedComparer.cs" />
     <Compile Include="Sorting\EpisodeCountComparer.cs" />
+    <Compile Include="Sorting\GameSystemComparer.cs" />
     <Compile Include="Sorting\IsFolderComparer.cs" />
     <Compile Include="Sorting\IsUnplayedComparer.cs" />
     <Compile Include="Sorting\MetascoreComparer.cs" />
@@ -235,6 +234,7 @@
     <Compile Include="Sorting\NameComparer.cs" />
     <Compile Include="Sorting\OfficialRatingComparer.cs" />
     <Compile Include="Sorting\PlayCountComparer.cs" />
+    <Compile Include="Sorting\PlayersComparer.cs" />
     <Compile Include="Sorting\PremiereDateComparer.cs" />
     <Compile Include="Sorting\ProductionYearComparer.cs" />
     <Compile Include="Sorting\RandomComparer.cs" />
@@ -248,6 +248,7 @@
     <Compile Include="Persistence\SqliteItemRepository.cs" />
     <Compile Include="Persistence\SqliteUserDataRepository.cs" />
     <Compile Include="Persistence\SqliteUserRepository.cs" />
+    <Compile Include="Sorting\StudioComparer.cs" />
     <Compile Include="Sorting\TrailerCountComparer.cs" />
     <Compile Include="Sorting\VideoBitRateComparer.cs" />
     <Compile Include="Themes\AppThemeManager.cs" />
@@ -378,6 +379,12 @@
       <Link>swagger-ui\swagger-ui.min.js</Link>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="x64\SQLite.Interop.dll" Condition=" '$(ConfigurationName)' != 'Release Mono' ">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="x86\SQLite.Interop.dll" Condition=" '$(ConfigurationName)' != 'Release Mono' ">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <EmbeddedResource Include="Localization\Ratings\be.txt" />
   </ItemGroup>
   <ItemGroup>

+ 54 - 0
MediaBrowser.Server.Implementations/Sorting/GameSystemComparer.cs

@@ -0,0 +1,54 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    public class GameSystemComparer : 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 game = x as Game;
+
+            if (game != null)
+            {
+                return game.GameSystem;
+            }
+
+            var system = x as GameSystem;
+
+            if (system != null)
+            {
+                return system.GameSystemName;
+            }
+
+            return string.Empty;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.GameSystem; }
+        }
+    }
+}

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

@@ -0,0 +1,46 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    public class PlayersComparer : 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 value.
+        /// </summary>
+        /// <param name="x">The x.</param>
+        /// <returns>System.String.</returns>
+        private int GetValue(BaseItem x)
+        {
+            var game = x as Game;
+
+            if (game != null)
+            {
+                return game.PlayersSupported ?? 0;
+            }
+
+            return 0;
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.Players; }
+        }
+    }
+}

+ 30 - 0
MediaBrowser.Server.Implementations/Sorting/StudioComparer.cs

@@ -0,0 +1,30 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+    public class StudioComparer : 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 AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty);
+        }
+
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get { return ItemSortBy.Studio; }
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Server.Implementations/packages.config

@@ -4,5 +4,5 @@
   <package id="MediaBrowser.BdInfo" version="1.0.0.10" targetFramework="net45" />
   <package id="Mono.Nat" version="1.2.3" targetFramework="net45" />
   <package id="morelinq" version="1.0.16006" targetFramework="net45" />
-  <package id="System.Data.SQLite.x86" version="1.0.90.0" targetFramework="net45" />
+  <package id="System.Data.SQLite.Core" version="1.0.91.3" targetFramework="net45" />
 </packages>

+ 15 - 1
MediaBrowser.ServerApplication/App.config

@@ -2,6 +2,8 @@
 <configuration>
   <configSections>
     <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
+    
+    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
   </configSections>
   <system.diagnostics>
     <assert assertuienabled="false" />
@@ -43,7 +45,7 @@
       </dependentAssembly>
       <dependentAssembly>
         <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-1.0.89.0" newVersion="1.0.89.0" />
+        <bindingRedirect oldVersion="0.0.0.0-1.0.91.0" newVersion="1.0.91.0" />
       </dependentAssembly>
       <dependentAssembly>
         <assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral" />
@@ -63,4 +65,16 @@
       </providers>
     </roleManager>
   </system.web>
+  <entityFramework>
+    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
+      <parameters>
+        <parameter value="v11.0" />
+      </parameters>
+    </defaultConnectionFactory>
+    <providers>
+      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
+      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
+    </providers>
+  </entityFramework>
+  
 </configuration>

+ 5 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -9,6 +9,7 @@ using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
@@ -36,6 +37,7 @@ using MediaBrowser.Model.Updates;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Server.Implementations;
 using MediaBrowser.Server.Implementations.BdInfo;
+using MediaBrowser.Server.Implementations.Collections;
 using MediaBrowser.Server.Implementations.Configuration;
 using MediaBrowser.Server.Implementations.Drawing;
 using MediaBrowser.Server.Implementations.Dto;
@@ -488,6 +490,9 @@ namespace MediaBrowser.ServerApplication
             var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
             RegisterSingleInstance<IAppThemeManager>(appThemeManager);
 
+            var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);
+            RegisterSingleInstance<ICollectionManager>(collectionManager);
+
             LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager);
             RegisterSingleInstance(LiveTvManager);
 

+ 6 - 0
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -235,6 +235,12 @@ xcopy "$(TargetDir)*.dll" "$(SolutionDir)..\Deploy\Server\System" /y
 mkdir "$(SolutionDir)..\Deploy\Server\System\dashboard-ui"
 xcopy "$(TargetDir)dashboard-ui" "$(SolutionDir)..\Deploy\Server\System\dashboard-ui" /y /s
 
+mkdir "$(SolutionDir)..\Deploy\Server\System\x86"
+xcopy "$(TargetDir)x86" "$(SolutionDir)..\Deploy\Server\System\x86" /y /s
+
+mkdir "$(SolutionDir)..\Deploy\Server\System\x64"
+xcopy "$(TargetDir)x64" "$(SolutionDir)..\Deploy\Server\System\x64" /y /s
+
 del "$(SolutionDir)..\Deploy\MBServer.zip"
 "$(SolutionDir)ThirdParty\7zip\7za" a -mx9 "$(SolutionDir)..\Deploy\MBServer.zip" "$(SolutionDir)..\Deploy\Server\*"
 )</PostBuildEvent>

+ 1 - 1
MediaBrowser.Tests/app.config

@@ -4,7 +4,7 @@
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
         <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-1.0.89.0" newVersion="1.0.89.0" />
+        <bindingRedirect oldVersion="0.0.0.0-1.0.91.0" newVersion="1.0.91.0" />
       </dependentAssembly>
     </assemblyBinding>
   </runtime>

+ 1 - 0
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -480,6 +480,7 @@ namespace MediaBrowser.WebDashboard.Api
                                       "dashboardinfo.js",
                                       "dashboardpage.js",
                                       "directorybrowser.js",
+                                      "editcollectionitems.js",
                                       "edititemmetadata.js",
                                       "edititempeople.js",
                                       "edititemimages.js",

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

@@ -213,6 +213,9 @@
     <Content Include="dashboard-ui\dashboardinfopage.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\editcollectionitems.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\encodingsettings.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -261,7 +264,7 @@
     <Content Include="dashboard-ui\allusersettings.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\boxsets.html">
+    <Content Include="dashboard-ui\collections.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     <Content Include="dashboard-ui\css\images\clients\mbkinect.png">
@@ -480,6 +483,9 @@
     <Content Include="dashboard-ui\scripts\dashboardinfo.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\editcollectionitems.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\encodingsettings.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>

+ 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.336</version>
+        <version>3.0.339</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 Theater 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.336" />
+            <dependency id="MediaBrowser.Common" version="3.0.339" />
             <dependency id="NLog" version="2.1.0" />
             <dependency id="SimpleInjector" version="2.4.1" />
             <dependency id="sharpcompress" version="0.10.2" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.336</version>
+        <version>3.0.339</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.336</version>
+        <version>3.0.339</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.336" />
+            <dependency id="MediaBrowser.Common" version="3.0.339" />
         </dependencies>
     </metadata>
     <files>