Browse Source

Added a completely separate DTOBaseItem to remove the ApiBaseItemWrapper mess and shrink json output size.

LukePulverenti Luke Pulverenti luke pulverenti 12 years ago
parent
commit
7835d690a1
31 changed files with 581 additions and 364 deletions
  1. 99 48
      MediaBrowser.Api/ApiService.cs
  2. 0 1
      MediaBrowser.Api/HttpHandlers/GenresHandler.cs
  3. 6 3
      MediaBrowser.Api/HttpHandlers/ItemHandler.cs
  4. 7 7
      MediaBrowser.Api/HttpHandlers/ItemListHandler.cs
  5. 0 1
      MediaBrowser.Api/HttpHandlers/StudiosHandler.cs
  6. 1 1
      MediaBrowser.Api/HttpHandlers/UsersHandler.cs
  7. 8 8
      MediaBrowser.Api/HttpHandlers/VideoHandler.cs
  8. 0 1
      MediaBrowser.Api/HttpHandlers/YearsHandler.cs
  9. 42 25
      MediaBrowser.ApiInteraction/ApiClient.cs
  10. 0 11
      MediaBrowser.ApiInteraction/IHttpClient.cs
  11. 0 1
      MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj
  12. 0 1
      MediaBrowser.Controller/Configuration/ServerConfiguration.cs
  13. 1 2
      MediaBrowser.Controller/Kernel.cs
  14. 4 1
      MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
  15. 85 47
      MediaBrowser.Controller/Xml/BaseItemXmlParser.cs
  16. 0 67
      MediaBrowser.Model/DTO/ApiBaseItem.cs
  17. 86 0
      MediaBrowser.Model/DTO/DTOBaseItem.cs
  18. 2 2
      MediaBrowser.Model/DTO/IBNItem.cs
  19. 31 34
      MediaBrowser.Model/Entities/BaseItem.cs
  20. 105 39
      MediaBrowser.Model/Entities/Folder.cs
  21. 57 0
      MediaBrowser.Model/Entities/IHasProviderIds.cs
  22. 14 0
      MediaBrowser.Model/Entities/ItemSpecialCounts.cs
  23. 1 10
      MediaBrowser.Model/Entities/Person.cs
  24. 15 0
      MediaBrowser.Model/Entities/User.cs
  25. 3 4
      MediaBrowser.Model/Entities/UserItemData.cs
  26. 8 9
      MediaBrowser.Model/Entities/Video.cs
  27. 5 3
      MediaBrowser.Model/MediaBrowser.Model.csproj
  28. 0 34
      MediaBrowser.Model/Users/User.cs
  29. 0 2
      MediaBrowser.Movies/Entities/Movie.cs
  30. 0 1
      MediaBrowser.TV/Entities/Season.cs
  31. 1 1
      MediaBrowser.TV/Metadata/SeriesXmlParser.cs

+ 99 - 48
MediaBrowser.Api/ApiService.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Api
 {
@@ -22,72 +21,91 @@ namespace MediaBrowser.Api
             return Kernel.Instance.GetItemById(guid);
         }
 
-        /// <summary>
-        /// Takes a BaseItem and returns the actual object that will be serialized by the api
-        /// </summary>
-        public static BaseItemContainer<BaseItem> GetSerializationObject(BaseItem item, bool includeChildren, Guid userId)
+        public static DTOBaseItem GetDTOBaseItem(BaseItem item, User user, 
+            bool includeChildren = true, 
+            bool includePeople = true)
         {
-            User user = Kernel.Instance.Users.First(u => u.Id == userId);
-
-            BaseItemContainer<BaseItem> wrapper = new BaseItemContainer<BaseItem>()
-            {
-                Item = item,
-                UserItemData = user.GetItemData(item.Id),
-                Type = item.GetType().Name,
-                IsFolder = (item is Folder)
-            };
-
-            if (string.IsNullOrEmpty(item.LogoImagePath))
-            {
-                wrapper.ParentLogoItemId = GetParentLogoItemId(item);
-            }
-
-            if (item.BackdropImagePaths == null || !item.BackdropImagePaths.Any())
+            DTOBaseItem dto = new DTOBaseItem();
+
+            dto.AspectRatio = item.AspectRatio;
+            dto.BackdropCount = item.BackdropImagePaths == null ? 0 : item.BackdropImagePaths.Count();
+            dto.DateCreated = item.DateCreated;
+            dto.DisplayMediaType = item.DisplayMediaType;
+            dto.Genres = item.Genres;
+            dto.HasArt = !string.IsNullOrEmpty(item.ArtImagePath);
+            dto.HasBanner = !string.IsNullOrEmpty(item.BannerImagePath);
+            dto.HasLogo = !string.IsNullOrEmpty(item.LogoImagePath);
+            dto.HasPrimaryImage = !string.IsNullOrEmpty(item.LogoImagePath);
+            dto.HasThumb = !string.IsNullOrEmpty(item.ThumbnailImagePath);
+            dto.Id = item.Id;
+            dto.IndexNumber = item.IndexNumber;
+            dto.IsFolder = item is Folder;
+            dto.LocalTrailerCount = item.LocalTrailers == null ? 0 : item.LocalTrailers.Count();
+            dto.Name = item.Name;
+            dto.OfficialRating = item.OfficialRating;
+            dto.Overview = item.Overview;
+
+            // If there are no backdrops, indicate what parent has them in case the UI wants to allow inheritance
+            if (dto.BackdropCount == 0)
             {
                 int backdropCount;
-                wrapper.ParentBackdropItemId = GetParentBackdropItemId(item, out backdropCount);
-                wrapper.ParentBackdropCount = backdropCount;
+                dto.ParentBackdropItemId = GetParentBackdropItemId(item, out backdropCount);
+                dto.ParentBackdropCount = backdropCount;
             }
 
             if (item.Parent != null)
             {
-                wrapper.ParentId = item.Parent.Id;
+                dto.ParentId = item.Parent.Id;
             }
 
-            if (includeChildren)
+            // If there is no logo, indicate what parent has one in case the UI wants to allow inheritance
+            if (!dto.HasLogo)
             {
-                var folder = item as Folder;
+                dto.ParentLogoItemId = GetParentLogoItemId(item);
+            }
 
-                if (folder != null)
-                {
-                    wrapper.Children = folder.GetParentalAllowedChildren(user).Select(c => GetSerializationObject(c, false, userId));
-                }
+            dto.Path = item.Path;
 
-                // Attach People by transforming them into BaseItemPerson (DTO)
-                if (item.People != null)
-                {
-                    wrapper.People = item.People.Select(p =>
-                    {
-                        BaseItemPerson baseItemPerson = new BaseItemPerson();
+            dto.PremiereDate = item.PremiereDate;
+            dto.ProductionYear = item.ProductionYear;
+            dto.ProviderIds = item.ProviderIds;
+            dto.RunTimeTicks = item.RunTimeTicks;
+            dto.SortName = item.SortName;
+            dto.Taglines = item.Taglines;
+            dto.TrailerUrl = item.TrailerUrl;
+            dto.Type = item.GetType().Name;
+            dto.UserRating = item.UserRating;
 
-                        baseItemPerson.PersonInfo = p;
+            dto.UserData = item.GetUserData(user);
 
-                        Person ibnObject = Kernel.Instance.ItemController.GetPerson(p.Name);
+            AttachStudios(dto, item);
 
-                        if (ibnObject != null)
-                        {
-                            baseItemPerson.PrimaryImagePath = ibnObject.PrimaryImagePath;
-                        }
+            if (includeChildren)
+            {
+                AttachChildren(dto, item, user);
+            }
 
-                        return baseItemPerson;
-                    });
-                }
+            if (includePeople)
+            {
+                AttachPeople(dto, item);
             }
 
+            Folder folder = item as Folder;
+
+            if (folder != null)
+            {
+                dto.SpecialCounts = folder.GetSpecialCounts(user);
+            }
+            
+            return dto;
+        }
+
+        private static void AttachStudios(DTOBaseItem dto, BaseItem item)
+        {
             // Attach Studios by transforming them into BaseItemStudio (DTO)
             if (item.Studios != null)
             {
-                wrapper.Studios = item.Studios.Select(s =>
+                dto.Studios = item.Studios.Select(s =>
                 {
                     BaseItemStudio baseItemStudio = new BaseItemStudio();
 
@@ -97,14 +115,47 @@ namespace MediaBrowser.Api
 
                     if (ibnObject != null)
                     {
-                        baseItemStudio.PrimaryImagePath = ibnObject.PrimaryImagePath;
+                        baseItemStudio.HasImage = !string.IsNullOrEmpty(ibnObject.PrimaryImagePath);
                     }
 
                     return baseItemStudio;
                 });
             }
+        }
+
+        private static void AttachChildren(DTOBaseItem dto, BaseItem item, User user)
+        {
+            var folder = item as Folder;
+
+            if (folder != null)
+            {
+                dto.Children = folder.GetParentalAllowedChildren(user).Select(c => GetDTOBaseItem(c, user, false, false));
+            }
 
-            return wrapper;
+            dto.LocalTrailers = item.LocalTrailers;
+        }
+
+        private static void AttachPeople(DTOBaseItem dto, BaseItem item)
+        {
+            // Attach People by transforming them into BaseItemPerson (DTO)
+            if (item.People != null)
+            {
+                dto.People = item.People.Select(p =>
+                {
+                    BaseItemPerson baseItemPerson = new BaseItemPerson();
+
+                    baseItemPerson.PersonInfo = p;
+
+                    Person ibnObject = Kernel.Instance.ItemController.GetPerson(p.Name);
+
+                    if (ibnObject != null)
+                    {
+                        baseItemPerson.HasImage = !string.IsNullOrEmpty(ibnObject.PrimaryImagePath);
+                    }
+
+                    return baseItemPerson;
+                });
+            }
         }
 
         private static Guid? GetParentBackdropItemId(BaseItem item, out int backdropCount)

+ 0 - 1
MediaBrowser.Api/HttpHandlers/GenresHandler.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Api.HttpHandlers
 {

+ 6 - 3
MediaBrowser.Api/HttpHandlers/ItemHandler.cs

@@ -1,15 +1,18 @@
 using System;
+using System.Linq;
 using MediaBrowser.Common.Net.Handlers;
+using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class ItemHandler : BaseJsonHandler<BaseItemContainer<BaseItem>>
+    public class ItemHandler : BaseJsonHandler<DTOBaseItem>
     {
-        protected sealed override BaseItemContainer<BaseItem> GetObjectToSerialize()
+        protected sealed override DTOBaseItem GetObjectToSerialize()
         {
             Guid userId = Guid.Parse(QueryString["userid"]);
+            User user = Kernel.Instance.Users.First(u => u.Id == userId);
 
             BaseItem item = ItemToSerialize;
 
@@ -18,7 +21,7 @@ namespace MediaBrowser.Api.HttpHandlers
                 return null;
             }
 
-            return ApiService.GetSerializationObject(item, true, userId);
+            return ApiService.GetDTOBaseItem(item, user);
         }
 
         protected virtual BaseItem ItemToSerialize

+ 7 - 7
MediaBrowser.Api/HttpHandlers/ItemListHandler.cs

@@ -5,18 +5,18 @@ using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class ItemListHandler : BaseJsonHandler<IEnumerable<BaseItemContainer<BaseItem>>>
+    public class ItemListHandler : BaseJsonHandler<IEnumerable<DTOBaseItem>>
     {
-        protected override IEnumerable<BaseItemContainer<BaseItem>> GetObjectToSerialize()
+        protected override IEnumerable<DTOBaseItem> GetObjectToSerialize()
         {
+            User user = Kernel.Instance.Users.First(u => u.Id == UserId);
+
             return ItemsToSerialize.Select(i =>
             {
-                return ApiService.GetSerializationObject(i, false, UserId);
-
+                return ApiService.GetDTOBaseItem(i, user, includeChildren: false, includePeople: false);
             });
         }
 
@@ -27,7 +27,7 @@ namespace MediaBrowser.Api.HttpHandlers
                 Folder parent = ApiService.GetItemById(ItemId) as Folder;
 
                 User user = Kernel.Instance.Users.First(u => u.Id == UserId);
-                
+
                 if (ListType.Equals("inprogressitems", StringComparison.OrdinalIgnoreCase))
                 {
                     return parent.GetInProgressItems(user);
@@ -76,7 +76,7 @@ namespace MediaBrowser.Api.HttpHandlers
                 return Guid.Parse(QueryString["userid"]);
             }
         }
-        
+
         private string ListType
         {
             get

+ 0 - 1
MediaBrowser.Api/HttpHandlers/StudiosHandler.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Api.HttpHandlers
 {

+ 1 - 1
MediaBrowser.Api/HttpHandlers/UsersHandler.cs

@@ -1,7 +1,7 @@
 using System.Collections.Generic;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
-using MediaBrowser.Model.Users;
+using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.HttpHandlers
 {

+ 8 - 8
MediaBrowser.Api/HttpHandlers/VideoHandler.cs

@@ -48,11 +48,11 @@ namespace MediaBrowser.Api.HttpHandlers
                 return true;
             }
 
-            AudioStream audio = LibraryItem.AudioStreams.FirstOrDefault();
+            AudioStream audioStream = (LibraryItem.AudioStreams ?? new AudioStream[] { }).FirstOrDefault();
 
-            if (audio != null)
+            if (audioStream != null)
             {
-                if (RequiresAudioConversion(audio))
+                if (RequiresAudioConversion(audioStream))
                 {
                     return true;
                 }
@@ -121,7 +121,7 @@ namespace MediaBrowser.Api.HttpHandlers
 
         private string GetAudioArguments(string outputFormat)
         {
-            AudioStream audioStream = LibraryItem.AudioStreams.FirstOrDefault();
+            AudioStream audioStream = (LibraryItem.AudioStreams ?? new AudioStream[] { }).FirstOrDefault();
 
             if (audioStream == null)
             {
@@ -261,7 +261,7 @@ namespace MediaBrowser.Api.HttpHandlers
                 }
             }
 
-            if (LibraryItem.VideoCodec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || LibraryItem.VideoCodec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1)
+            if (LibraryItem.Codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || LibraryItem.Codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1)
             {
                 return false;
             }
@@ -279,15 +279,15 @@ namespace MediaBrowser.Api.HttpHandlers
                 }
             }
 
-            if (audio.AudioFormat.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1)
+            if (audio.Format.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1)
             {
                 return false;
             }
-            if (audio.AudioFormat.IndexOf("ac-3", StringComparison.OrdinalIgnoreCase) != -1 || audio.AudioFormat.IndexOf("ac3", StringComparison.OrdinalIgnoreCase) != -1)
+            if (audio.Format.IndexOf("ac-3", StringComparison.OrdinalIgnoreCase) != -1 || audio.Format.IndexOf("ac3", StringComparison.OrdinalIgnoreCase) != -1)
             {
                 return false;
             }
-            if (audio.AudioFormat.IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1 || audio.AudioFormat.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
+            if (audio.Format.IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1 || audio.Format.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
             {
                 return false;
             }

+ 0 - 1
MediaBrowser.Api/HttpHandlers/YearsHandler.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Api.HttpHandlers
 {

+ 42 - 25
MediaBrowser.ApiInteraction/ApiClient.cs

@@ -1,16 +1,23 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
+using System.Net;
+using System.Net.Http;
 using System.Threading.Tasks;
 using MediaBrowser.Model.DTO;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.ApiInteraction
 {
     public class ApiClient : IDisposable
     {
+        public ApiClient(HttpClientHandler handler)
+        {
+            handler.AutomaticDecompression = DecompressionMethods.Deflate;
+
+            HttpClient = new HttpClient(handler);
+        }
+
         /// <summary>
         /// Gets or sets the server host name (myserver or 192.168.x.x)
         /// </summary>
@@ -32,7 +39,7 @@ namespace MediaBrowser.ApiInteraction
             }
         }
 
-        public IHttpClient HttpClient { get; set; }
+        public HttpClient HttpClient { get; private set; }
         public IJsonSerializer JsonSerializer { get; set; }
 
         /// <summary>
@@ -84,26 +91,26 @@ namespace MediaBrowser.ApiInteraction
         /// <summary>
         /// This is a helper to get a list of backdrop url's from a given ApiBaseItemWrapper. If the actual item does not have any backdrops it will return backdrops from the first parent that does.
         /// </summary>
-        /// <param name="itemWrapper">A given item.</param>
+        /// <param name="item">A given item.</param>
         /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
         /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
         /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
         /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
         /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
-        public IEnumerable<string> GetBackdropImageUrls(ApiBaseItemContainer itemWrapper, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
+        public IEnumerable<string> GetBackdropImageUrls(DTOBaseItem item, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
         {
             Guid? backdropItemId = null;
             int backdropCount = 0;
 
-            if (itemWrapper.Item.BackdropImagePaths == null || !itemWrapper.Item.BackdropImagePaths.Any())
+            if (item.BackdropCount == 0)
             {
-                backdropItemId = itemWrapper.ParentBackdropItemId;
-                backdropCount = itemWrapper.ParentBackdropCount ?? 0;
+                backdropItemId = item.ParentBackdropItemId;
+                backdropCount = item.ParentBackdropCount ?? 0;
             }
             else
             {
-                backdropItemId = itemWrapper.Item.Id;
-                backdropCount = itemWrapper.Item.BackdropImagePaths.Count();
+                backdropItemId = item.Id;
+                backdropCount = item.BackdropCount;
             }
 
             if (backdropItemId == null)
@@ -124,15 +131,15 @@ namespace MediaBrowser.ApiInteraction
         /// <summary>
         /// This is a helper to get the logo image url from a given ApiBaseItemWrapper. If the actual item does not have a logo, it will return the logo from the first parent that does, or null.
         /// </summary>
-        /// <param name="itemWrapper">A given item.</param>
+        /// <param name="item">A given item.</param>
         /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
         /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
         /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
         /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
         /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
-        public string GetLogoImageUrl(ApiBaseItemContainer itemWrapper, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
+        public string GetLogoImageUrl(DTOBaseItem item, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
         {
-            Guid? logoItemId = !string.IsNullOrEmpty(itemWrapper.Item.LogoImagePath) ? itemWrapper.Item.Id : itemWrapper.ParentLogoItemId;
+            Guid? logoItemId = item.HasLogo ? item.Id : item.ParentLogoItemId;
 
             if (logoItemId.HasValue)
             {
@@ -153,7 +160,7 @@ namespace MediaBrowser.ApiInteraction
         /// <summary>
         /// Gets a BaseItem
         /// </summary>
-        public async Task<ApiBaseItemContainer> GetItemAsync(Guid id, Guid userId)
+        public async Task<DTOBaseItem> GetItemAsync(Guid id, Guid userId)
         {
             string url = ApiUrl + "/item?userId=" + userId.ToString();
 
@@ -164,7 +171,7 @@ namespace MediaBrowser.ApiInteraction
 
             using (Stream stream = await HttpClient.GetStreamAsync(url))
             {
-                return JsonSerializer.DeserializeFromStream<ApiBaseItemContainer>(stream);
+                return JsonSerializer.DeserializeFromStream<DTOBaseItem>(stream);
             }
         }
 
@@ -210,44 +217,54 @@ namespace MediaBrowser.ApiInteraction
         /// <summary>
         /// Gets all items that contain a given Year
         /// </summary>
-        public async Task<IEnumerable<ApiBaseItemContainer>> GetItemsWithYearAsync(string name, Guid userId)
+        public async Task<IEnumerable<DTOBaseItem>> GetItemsWithYearAsync(string name, Guid userId)
         {
             string url = ApiUrl + "/itemlist?listtype=itemswithyear&userId=" + userId.ToString() + "&name=" + name;
 
             using (Stream stream = await HttpClient.GetStreamAsync(url))
             {
-                return JsonSerializer.DeserializeFromStream<IEnumerable<ApiBaseItemContainer>>(stream);
+                return JsonSerializer.DeserializeFromStream<IEnumerable<DTOBaseItem>>(stream);
             }
         }
 
         /// <summary>
         /// Gets all items that contain a given Genre
         /// </summary>
-        public async Task<IEnumerable<ApiBaseItemContainer>> GetItemsWithGenreAsync(string name, Guid userId)
+        public async Task<IEnumerable<DTOBaseItem>> GetItemsWithGenreAsync(string name, Guid userId)
         {
             string url = ApiUrl + "/itemlist?listtype=itemswithgenre&userId=" + userId.ToString() + "&name=" + name;
 
             using (Stream stream = await HttpClient.GetStreamAsync(url))
             {
-                return JsonSerializer.DeserializeFromStream<IEnumerable<ApiBaseItemContainer>>(stream);
+                return JsonSerializer.DeserializeFromStream<IEnumerable<DTOBaseItem>>(stream);
             }
         }
 
         /// <summary>
         /// Gets all items that contain a given Person
         /// </summary>
-        public async Task<IEnumerable<ApiBaseItemContainer>> GetItemsWithPersonAsync(string name, PersonType? personType, Guid userId)
+        public async Task<IEnumerable<DTOBaseItem>> GetItemsWithPersonAsync(string name, Guid userId)
         {
             string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name;
 
-            if (personType.HasValue)
+            using (Stream stream = await HttpClient.GetStreamAsync(url))
             {
-                url += "&persontype=" + personType.Value.ToString();
+                return JsonSerializer.DeserializeFromStream<IEnumerable<DTOBaseItem>>(stream);
             }
+        }
+        
+        /// <summary>
+        /// Gets all items that contain a given Person
+        /// </summary>
+        public async Task<IEnumerable<DTOBaseItem>> GetItemsWithPersonAsync(string name, string personType, Guid userId)
+        {
+            string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name;
+
+            url += "&persontype=" + personType;
 
             using (Stream stream = await HttpClient.GetStreamAsync(url))
             {
-                return JsonSerializer.DeserializeFromStream<IEnumerable<ApiBaseItemContainer>>(stream);
+                return JsonSerializer.DeserializeFromStream<IEnumerable<DTOBaseItem>>(stream);
             }
         }
 
@@ -267,13 +284,13 @@ namespace MediaBrowser.ApiInteraction
         /// <summary>
         /// Gets all items that contain a given Studio
         /// </summary>
-        public async Task<IEnumerable<ApiBaseItemContainer>> GetItemsWithStudioAsync(string name, Guid userId)
+        public async Task<IEnumerable<DTOBaseItem>> GetItemsWithStudioAsync(string name, Guid userId)
         {
             string url = ApiUrl + "/itemlist?listtype=itemswithstudio&userId=" + userId.ToString() + "&name=" + name;
 
             using (Stream stream = await HttpClient.GetStreamAsync(url))
             {
-                return JsonSerializer.DeserializeFromStream<IEnumerable<ApiBaseItemContainer>>(stream);
+                return JsonSerializer.DeserializeFromStream<IEnumerable<DTOBaseItem>>(stream);
             }
         }
 

+ 0 - 11
MediaBrowser.ApiInteraction/IHttpClient.cs

@@ -1,11 +0,0 @@
-using System;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.ApiInteraction
-{
-    public interface IHttpClient : IDisposable  
-    {
-        Task<Stream> GetStreamAsync(string url);
-    }
-}

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

@@ -40,7 +40,6 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="ApiClient.cs" />
-    <Compile Include="IHttpClient.cs" />
     <Compile Include="IJsonSerializer.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>

+ 0 - 1
MediaBrowser.Controller/Configuration/ServerConfiguration.cs

@@ -5,6 +5,5 @@ namespace MediaBrowser.Controller.Configuration
 {
     public class ServerConfiguration : BaseApplicationConfiguration
     {
-        public string ImagesByNamePath { get; set; }
     }
 }

+ 1 - 2
MediaBrowser.Controller/Kernel.cs

@@ -14,7 +14,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Progress;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller
 {
@@ -193,7 +192,7 @@ namespace MediaBrowser.Controller
                 return RootFolder;
             }
 
-            return RootFolder.FindById(id);
+            return RootFolder.FindItemById(id);
         }
 
         /// <summary>

+ 4 - 1
MediaBrowser.Controller/Resolvers/BaseItemResolver.cs

@@ -137,7 +137,10 @@ namespace MediaBrowser.Controller.Resolvers
                 }
             }
 
-            item.BackdropImagePaths = backdropFiles;
+            if (backdropFiles.Any())
+            {
+                item.BackdropImagePaths = backdropFiles;
+            }
         }
 
         protected virtual void PopulateLocalTrailers(T item, ItemResolveEventArgs args)

+ 85 - 47
MediaBrowser.Controller/Xml/BaseItemXmlParser.cs

@@ -101,8 +101,24 @@ namespace MediaBrowser.Controller.Xml
                     break;
 
                 case "TagLine":
-                    item.Tagline = reader.ReadString();
-                    break;
+                    {
+                        var list = (item.Taglines ?? new string[] { }).ToList();
+                        var tagline = reader.ReadString();
+
+                        if (!list.Contains(tagline))
+                        {
+                            list.Add(tagline);
+                        }
+
+                        item.Taglines = list;
+                        break;
+                    }
+
+                case "TagLines":
+                    {
+                        FetchFromTaglinesNode(reader.ReadSubtree(), item);
+                        break;
+                    }
 
                 case "ContentRating":
                 case "MPAARating":
@@ -138,7 +154,7 @@ namespace MediaBrowser.Controller.Xml
                 case "Director":
                     {
                         var list = (item.People ?? new PersonInfo[] { }).ToList();
-                        list.AddRange(GetSplitValues(reader.ReadString(), '|').Select(v => new PersonInfo() { Name = v, PersonType = PersonType.Director }));
+                        list.AddRange(GetSplitValues(reader.ReadString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Director" }));
 
                         item.People = list;
                         break;
@@ -146,7 +162,7 @@ namespace MediaBrowser.Controller.Xml
                 case "Writer":
                     {
                         var list = (item.People ?? new PersonInfo[] { }).ToList();
-                        list.AddRange(GetSplitValues(reader.ReadString(), '|').Select(v => new PersonInfo() { Name = v, PersonType = PersonType.Writer }));
+                        list.AddRange(GetSplitValues(reader.ReadString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Writer" }));
 
                         item.People = list;
                         break;
@@ -156,7 +172,7 @@ namespace MediaBrowser.Controller.Xml
                 case "GuestStars":
                     {
                         var list = (item.People ?? new PersonInfo[] { }).ToList();
-                        list.AddRange(GetSplitValues(reader.ReadString(), '|').Select(v => new PersonInfo() { Name = v, PersonType = PersonType.Actor }));
+                        list.AddRange(GetSplitValues(reader.ReadString(), '|').Select(v => new PersonInfo() { Name = v, Type = "Actor" }));
 
                         item.People = list;
                         break;
@@ -287,7 +303,7 @@ namespace MediaBrowser.Controller.Xml
                             {
                                 AudioStream stream = FetchMediaInfoAudio(reader.ReadSubtree());
 
-                                List<AudioStream> streams = item.AudioStreams.ToList();
+                                List<AudioStream> streams = (item.AudioStreams ?? new AudioStream[] { }).ToList();
                                 streams.Add(stream);
                                 item.AudioStreams = streams;
 
@@ -322,6 +338,14 @@ namespace MediaBrowser.Controller.Xml
                 {
                     switch (reader.Name)
                     {
+                        case "Default":
+                            stream.IsDefault = reader.ReadString() == "True";
+                            break;
+
+                        case "Forced":
+                            stream.IsForced = reader.ReadString() == "True";
+                            break;
+
                         case "BitRate":
                             stream.BitRate = reader.ReadIntSafe();
                             break;
@@ -343,40 +367,40 @@ namespace MediaBrowser.Controller.Xml
                                     case "dts-es":
                                     case "dts-es matrix":
                                     case "dts-es discrete":
-                                        stream.AudioFormat = "DTS";
-                                        stream.AudioProfile = "ES";
+                                        stream.Format = "DTS";
+                                        stream.Profile = "ES";
                                         break;
                                     case "dts-hd hra":
                                     case "dts-hd high resolution":
-                                        stream.AudioFormat = "DTS";
-                                        stream.AudioProfile = "HRA";
+                                        stream.Format = "DTS";
+                                        stream.Profile = "HRA";
                                         break;
                                     case "dts ma":
                                     case "dts-hd ma":
                                     case "dts-hd master":
-                                        stream.AudioFormat = "DTS";
-                                        stream.AudioProfile = "MA";
+                                        stream.Format = "DTS";
+                                        stream.Profile = "MA";
                                         break;
                                     case "dolby digital":
                                     case "dolby digital surround ex":
                                     case "dolby surround":
-                                        stream.AudioFormat = "AC-3";
+                                        stream.Format = "AC-3";
                                         break;
                                     case "dolby digital plus":
-                                        stream.AudioFormat = "E-AC-3";
+                                        stream.Format = "E-AC-3";
                                         break;
                                     case "dolby truehd":
-                                        stream.AudioFormat = "AC-3";
-                                        stream.AudioProfile = "TrueHD";
+                                        stream.Format = "AC-3";
+                                        stream.Profile = "TrueHD";
                                         break;
                                     case "mp2":
-                                        stream.AudioFormat = "MPEG Audio";
-                                        stream.AudioProfile = "Layer 2";
+                                        stream.Format = "MPEG Audio";
+                                        stream.Profile = "Layer 2";
                                         break;
                                     case "other":
                                         break;
                                     default:
-                                        stream.AudioFormat = codec;
+                                        stream.Format = codec;
                                         break;
                                 }
 
@@ -412,7 +436,7 @@ namespace MediaBrowser.Controller.Xml
                             break;
 
                         case "BitRate":
-                            item.VideoBitRate = reader.ReadIntSafe();
+                            item.BitRate = reader.ReadIntSafe();
                             break;
 
                         case "FrameRate":
@@ -424,14 +448,14 @@ namespace MediaBrowser.Controller.Xml
                             break;
 
                         case "Duration":
-                            item.RunTimeInMilliseconds = reader.ReadIntSafe() * 60000;
+                            item.RunTimeTicks = TimeSpan.FromMinutes(reader.ReadIntSafe()).Ticks;
                             break;
 
                         case "DurationSeconds":
                             int seconds = reader.ReadIntSafe();
                             if (seconds > 0)
                             {
-                                item.RunTimeInMilliseconds = seconds * 1000;
+                                item.RunTimeTicks = TimeSpan.FromSeconds(seconds).Ticks;
                             }
                             break;
 
@@ -442,16 +466,16 @@ namespace MediaBrowser.Controller.Xml
                                 switch (videoCodec.ToLower())
                                 {
                                     case "sorenson h.263":
-                                        item.VideoCodec = "Sorenson H263";
+                                        item.Codec = "Sorenson H263";
                                         break;
                                     case "h.262":
-                                        item.VideoCodec = "MPEG-2 Video";
+                                        item.Codec = "MPEG-2 Video";
                                         break;
                                     case "h.264":
-                                        item.VideoCodec = "AVC";
+                                        item.Codec = "AVC";
                                         break;
                                     default:
-                                        item.VideoCodec = videoCodec;
+                                        item.Codec = videoCodec;
                                         break;
                                 }
 
@@ -499,6 +523,39 @@ namespace MediaBrowser.Controller.Xml
             item.Subtitles = list;
         }
 
+        private void FetchFromTaglinesNode(XmlReader reader, T item)
+        {
+            List<string> list = (item.Taglines ?? new string[] { }).ToList();
+
+            reader.MoveToContent();
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "Tagline":
+                            {
+                                string val = reader.ReadString();
+
+                                if (!string.IsNullOrWhiteSpace(val))
+                                {
+                                    list.Add(val);
+                                }
+                                break;
+                            }
+
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+
+            item.Taglines = list;
+        }
+
         private void FetchFromGenresNode(XmlReader reader, T item)
         {
             List<string> list = (item.Genres ?? new string[] { }).ToList();
@@ -668,27 +725,8 @@ namespace MediaBrowser.Controller.Xml
                             break;
 
                         case "Type":
-                            {
-                                string type = reader.ReadString();
-
-                                if (type.Equals("Director", StringComparison.OrdinalIgnoreCase))
-                                {
-                                    person.PersonType = PersonType.Director;
-                                }
-                                else if (type.Equals("Actor", StringComparison.OrdinalIgnoreCase))
-                                {
-                                    person.PersonType = PersonType.Actor;
-                                }
-                                else if (type.Equals("Writer", StringComparison.OrdinalIgnoreCase))
-                                {
-                                    person.PersonType = PersonType.Writer;
-                                }
-                                else if (type.Equals("Producer", StringComparison.OrdinalIgnoreCase))
-                                {
-                                    person.PersonType = PersonType.Producer;
-                                }
-                                break;
-                            }
+                            person.Type = reader.ReadString();
+                            break;
 
                         case "Role":
                             person.Overview = reader.ReadString();

+ 0 - 67
MediaBrowser.Model/DTO/ApiBaseItem.cs

@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Users;
-
-namespace MediaBrowser.Model.DTO
-{
-    /// <summary>
-    /// This is a concrete class that the UI can use to deserialize
-    /// It is flat in the sense that it will be used regardless of the type of BaseItem involved
-    /// </summary>
-    public class ApiBaseItem : BaseItem
-    {
-        // Series properties
-        public string Status { get; set; }
-        public IEnumerable<DayOfWeek> AirDays { get; set; }
-        public string AirTime { get; set; }
-    }
-
-    /// <summary>
-    /// This is the full return object when requesting an Item
-    /// </summary>
-    public class BaseItemContainer<TItemType>
-        where TItemType : BaseItem
-    {
-        public TItemType Item { get; set; }
-
-        public UserItemData UserItemData { get; set; }
-
-        public IEnumerable<BaseItemContainer<TItemType>> Children { get; set; }
-
-        public bool IsFolder { get; set; }
-
-        public Guid? ParentId { get; set; }
-
-        public string Type { get; set; }
-
-        public bool IsType(Type type)
-        {
-            return IsType(type.Name);
-        }
-
-        public bool IsType(string type)
-        {
-            return Type.Equals(type, StringComparison.OrdinalIgnoreCase);
-        }
-
-        public IEnumerable<BaseItemPerson> People { get; set; }
-        public IEnumerable<BaseItemStudio> Studios { get; set; }
-
-        /// <summary>
-        /// If the item does not have a logo, this will hold the Id of the Parent that has one.
-        /// </summary>
-        public Guid? ParentLogoItemId { get; set; }
-
-        public Guid? ParentBackdropItemId { get; set; }
-
-        public int? ParentBackdropCount { get; set; }
-    }
-
-    /// <summary>
-    /// This is strictly for convenience so the UI's don't have to use the verbose generic syntax of BaseItemWrapper<ApiBaseItem>
-    /// </summary>
-    public class ApiBaseItemContainer : BaseItemContainer<ApiBaseItem>
-    {
-    }
-}

+ 86 - 0
MediaBrowser.Model/DTO/DTOBaseItem.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.DTO
+{
+    public class DTOBaseItem : IHasProviderIds
+    {
+        public string Name { get; set; }
+        public Guid Id { get; set; }
+        public DateTime DateCreated { get; set; }
+
+        public string SortName { get; set; }
+        public DateTime? PremiereDate { get; set; }
+        public string Path { get; set; }
+        public string OfficialRating { get; set; }
+        public string Overview { get; set; }
+        public IEnumerable<string> Taglines { get; set; }
+
+        public IEnumerable<string> Genres { get; set; }
+
+        public string DisplayMediaType { get; set; }
+
+        public float? UserRating { get; set; }
+        public long? RunTimeTicks { get; set; }
+
+        public string AspectRatio { get; set; }
+        public int? ProductionYear { get; set; }
+
+        public int? IndexNumber { get; set; }
+
+        public string TrailerUrl { get; set; }
+
+        public Dictionary<string, string> ProviderIds { get; set; }
+
+        public bool HasBanner { get; set; }
+        public bool HasArt { get; set; }
+        public bool HasLogo { get; set; }
+        public bool HasThumb { get; set; }
+        public bool HasPrimaryImage { get; set; }
+
+        public int BackdropCount { get; set; }
+
+        public IEnumerable<DTOBaseItem> Children { get; set; }
+
+        public bool IsFolder { get; set; }
+
+        public Guid? ParentId { get; set; }
+
+        public string Type { get; set; }
+
+        public IEnumerable<BaseItemPerson> People { get; set; }
+        public IEnumerable<BaseItemStudio> Studios { get; set; }
+
+        /// <summary>
+        /// If the item does not have a logo, this will hold the Id of the Parent that has one.
+        /// </summary>
+        public Guid? ParentLogoItemId { get; set; }
+
+        /// <summary>
+        /// If the item does not have any backdrops, this will hold the Id of the Parent that has one.
+        /// </summary>
+        public Guid? ParentBackdropItemId { get; set; }
+        public int? ParentBackdropCount { get; set; }
+
+        public IEnumerable<Video> LocalTrailers { get; set; }
+        public int LocalTrailerCount { get; set; }
+
+        /// <summary>
+        /// User data for this item based on the user it's being requested for
+        /// </summary>
+        public UserItemData UserData { get; set; }
+
+        public ItemSpecialCounts SpecialCounts { get; set; }
+
+        public bool IsType(Type type)
+        {
+            return IsType(type.Name);
+        }
+
+        public bool IsType(string type)
+        {
+            return Type.Equals(type, StringComparison.OrdinalIgnoreCase);
+        }
+    }
+}

+ 2 - 2
MediaBrowser.Model/DTO/IBNItem.cs

@@ -24,7 +24,7 @@ namespace MediaBrowser.Model.DTO
     public class BaseItemPerson
     {
         public PersonInfo PersonInfo { get; set; }
-        public string PrimaryImagePath { get; set; }
+        public bool HasImage { get; set; }
     }
 
     /// <summary>
@@ -33,6 +33,6 @@ namespace MediaBrowser.Model.DTO
     public class BaseItemStudio
     {
         public string Name { get; set; }
-        public string PrimaryImagePath { get; set; }
+        public bool HasImage { get; set; }
     }
 }

+ 31 - 34
MediaBrowser.Model/Entities/BaseItem.cs

@@ -1,11 +1,10 @@
 using System;
 using System.Collections.Generic;
-using System.Runtime.Serialization;
-using MediaBrowser.Model.Users;
+using System.Linq;
 
 namespace MediaBrowser.Model.Entities
 {
-    public abstract class BaseItem : BaseEntity
+    public abstract class BaseItem : BaseEntity, IHasProviderIds
     {
         public string SortName { get; set; }
 
@@ -16,25 +15,25 @@ namespace MediaBrowser.Model.Entities
 
         public string Path { get; set; }
 
-        [IgnoreDataMember]
         public Folder Parent { get; set; }
 
         public string LogoImagePath { get; set; }
+
         public string ArtImagePath { get; set; }
+
         public string ThumbnailImagePath { get; set; }
+
         public string BannerImagePath { get; set; }
 
         public IEnumerable<string> BackdropImagePaths { get; set; }
 
         public string OfficialRating { get; set; }
         
-        [IgnoreDataMember]
         public string CustomRating { get; set; }
 
         public string Overview { get; set; }
-        public string Tagline { get; set; }
+        public IEnumerable<string> Taglines { get; set; }
 
-        [IgnoreDataMember]
         public IEnumerable<PersonInfo> People { get; set; }
 
         public IEnumerable<string> Studios { get; set; }
@@ -44,7 +43,7 @@ namespace MediaBrowser.Model.Entities
         public string DisplayMediaType { get; set; }
 
         public float? UserRating { get; set; }
-        public int? RunTimeInMilliseconds { get; set; }
+        public long? RunTimeTicks { get; set; }
 
         public string AspectRatio { get; set; }
         public int? ProductionYear { get; set; }
@@ -61,54 +60,52 @@ namespace MediaBrowser.Model.Entities
 
         public Dictionary<string, string> ProviderIds { get; set; }
 
-        /// <summary>
-        /// Gets a provider id
-        /// </summary>
-        public string GetProviderId(MetadataProviders provider)
-        {
-            return GetProviderId(provider.ToString());
-        }
+        public Dictionary<Guid, UserItemData> UserData { get; set; }
 
-        /// <summary>
-        /// Gets a provider id
-        /// </summary>
-        public string GetProviderId(string name)
+        public UserItemData GetUserData(User user)
         {
-            if (ProviderIds == null)
+            if (UserData == null || !UserData.ContainsKey(user.Id))
             {
                 return null;
             }
 
-            return ProviderIds[name];
+            return UserData[user.Id];
         }
 
-        /// <summary>
-        /// Sets a provider id
-        /// </summary>
-        public void SetProviderId(string name, string value)
+        public void AddUserData(User user, UserItemData data)
         {
-            if (ProviderIds == null)
+            if (UserData == null)
             {
-                ProviderIds = new Dictionary<string, string>();
+                UserData = new Dictionary<Guid, UserItemData>();
             }
 
-            ProviderIds[name] = value;
+            UserData[user.Id] = data;
         }
 
         /// <summary>
-        /// Sets a provider id
+        /// Determines if a given user has access to this item
         /// </summary>
-        public void SetProviderId(MetadataProviders provider, string value)
+        internal bool IsParentalAllowed(User user)
         {
-            SetProviderId(provider.ToString(), value);
+            return true;
         }
 
         /// <summary>
-        /// Determines if a given user has access to this item
+        /// Finds an item by ID, recursively
         /// </summary>
-        internal bool IsParentalAllowed(User user)
+        public virtual BaseItem FindItemById(Guid id)
         {
-            return true;
+            if (Id == id)
+            {
+                return this;
+            }
+
+            if (LocalTrailers != null)
+            {
+                return LocalTrailers.FirstOrDefault(i => i.Id == id);
+            }
+
+            return null;
         }
     }
 }

+ 105 - 39
MediaBrowser.Model/Entities/Folder.cs

@@ -1,8 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Runtime.Serialization;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Model.Entities
 {
@@ -18,7 +16,6 @@ namespace MediaBrowser.Model.Entities
             }
         }
 
-        [IgnoreDataMember]
         public BaseItem[] Children { get; set; }
 
         /// <summary>
@@ -50,6 +47,23 @@ namespace MediaBrowser.Model.Entities
             }
         }
 
+        /// <summary>
+        /// Since it can be slow to make all of these calculations at once, this method will provide a way to get them all back together
+        /// </summary>
+        public ItemSpecialCounts GetSpecialCounts(User user)
+        {
+            ItemSpecialCounts counts = new ItemSpecialCounts();
+
+            IEnumerable<BaseItem> recursiveChildren = GetParentalAllowedRecursiveChildren(user);
+
+            counts.RecentlyAddedItemCount = GetRecentlyAddedItems(recursiveChildren, user).Count();
+            counts.RecentlyAddedUnPlayedItemCount = GetRecentlyAddedUnplayedItems(recursiveChildren, user).Count();
+            counts.InProgressItemCount = GetInProgressItems(recursiveChildren, user).Count();
+            counts.WatchedPercentage = GetWatchedPercentage(recursiveChildren, user);
+
+            return counts;
+        }
+
         /// <summary>
         /// Finds all recursive items within a top-level parent that contain the given genre and are allowed for the current user
         /// </summary>
@@ -74,24 +88,33 @@ namespace MediaBrowser.Model.Entities
             return GetParentalAllowedRecursiveChildren(user).Where(f => f.Studios != null && f.Studios.Any(s => s.Equals(studio, StringComparison.OrdinalIgnoreCase)));
         }
 
+        /// <summary>
+        /// Finds all recursive items within a top-level parent that contain the given person and are allowed for the current user
+        /// </summary>
+        public IEnumerable<BaseItem> GetItemsWithPerson(string person, User user)
+        {
+            return GetParentalAllowedRecursiveChildren(user).Where(c =>
+            {
+                if (c.People != null)
+                {
+                    return c.People.Any(p => p.Name.Equals(person, StringComparison.OrdinalIgnoreCase));
+                }
+
+                return false;
+            });
+        }
+
         /// <summary>
         /// Finds all recursive items within a top-level parent that contain the given person and are allowed for the current user
         /// </summary>
         /// <param name="personType">Specify this to limit results to a specific PersonType</param>
-        public IEnumerable<BaseItem> GetItemsWithPerson(string person, PersonType? personType, User user)
+        public IEnumerable<BaseItem> GetItemsWithPerson(string person, string personType, User user)
         {
             return GetParentalAllowedRecursiveChildren(user).Where(c =>
             {
                 if (c.People != null)
                 {
-                    if (personType.HasValue)
-                    {
-                        return c.People.Any(p => p.Name.Equals(person, StringComparison.OrdinalIgnoreCase) && p.PersonType == personType.Value);
-                    }
-                    else
-                    {
-                        return c.People.Any(p => p.Name.Equals(person, StringComparison.OrdinalIgnoreCase));
-                    }
+                    return c.People.Any(p => p.Name.Equals(person, StringComparison.OrdinalIgnoreCase) && p.Type == personType);
                 }
 
                 return false;
@@ -103,9 +126,7 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         public IEnumerable<BaseItem> GetRecentlyAddedItems(User user)
         {
-            DateTime now = DateTime.Now;
-
-            return GetParentalAllowedRecursiveChildren(user).Where(i => !(i is Folder) && (now - i.DateCreated).TotalDays < user.RecentItemDays);
+            return GetRecentlyAddedItems(GetParentalAllowedRecursiveChildren(user), user);
         }
 
         /// <summary>
@@ -113,12 +134,7 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         public IEnumerable<BaseItem> GetRecentlyAddedUnplayedItems(User user)
         {
-            return GetRecentlyAddedItems(user).Where(i =>
-            {
-                var userdata = user.GetItemData(i.Id);
-
-                return userdata == null || userdata.PlayCount == 0;
-            });
+            return GetRecentlyAddedUnplayedItems(GetParentalAllowedRecursiveChildren(user), user);
         }
 
         /// <summary>
@@ -126,45 +142,95 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         public IEnumerable<BaseItem> GetInProgressItems(User user)
         {
-            return GetParentalAllowedRecursiveChildren(user).Where(i =>
+            return GetInProgressItems(GetParentalAllowedRecursiveChildren(user), user);
+        }
+
+        private static IEnumerable<BaseItem> GetRecentlyAddedItems(IEnumerable<BaseItem> itemSet, User user)
+        {
+            DateTime now = DateTime.Now;
+
+            return itemSet.Where(i => !(i is Folder) && (now - i.DateCreated).TotalDays < user.RecentItemDays);
+        }
+
+        private static IEnumerable<BaseItem> GetRecentlyAddedUnplayedItems(IEnumerable<BaseItem> itemSet, User user)
+        {
+            return GetRecentlyAddedItems(itemSet, user).Where(i =>
+            {
+                var userdata = i.GetUserData(user);
+
+                return userdata == null || userdata.PlayCount == 0;
+            });
+        }
+
+        private static IEnumerable<BaseItem> GetInProgressItems(IEnumerable<BaseItem> itemSet, User user)
+        {
+            return itemSet.Where(i =>
             {
                 if (i is Folder)
                 {
                     return false;
                 }
 
-                var userdata = user.GetItemData(i.Id);
+                var userdata = i.GetUserData(user);
 
-                return userdata != null && userdata.PlaybackPosition.Ticks > 0;
+                return userdata != null && userdata.PlaybackPositionTicks > 0;
             });
         }
 
+        private static decimal GetWatchedPercentage(IEnumerable<BaseItem> itemSet, User user)
+        {
+            itemSet = itemSet.Where(i => !(i is Folder));
+
+            if (!itemSet.Any())
+            {
+                return 0;
+            }
+
+            decimal totalPercent = 0;
+
+            foreach (BaseItem item in itemSet)
+            {
+                UserItemData data = item.GetUserData(user);
+
+                if (data == null)
+                {
+                    continue;
+                }
+
+                if (data.PlayCount > 0)
+                {
+                    totalPercent += 100;
+                }
+                else if (data.PlaybackPositionTicks > 0 && item.RunTimeTicks.HasValue)
+                {
+                    decimal itemPercent = data.PlaybackPositionTicks;
+                    itemPercent /= item.RunTimeTicks.Value;
+                    totalPercent += itemPercent;
+                }
+            }
+
+            return totalPercent / itemSet.Count();
+        }
+        
         /// <summary>
         /// Finds an item by ID, recursively
         /// </summary>
-        public BaseItem FindById(Guid id)
+        public override BaseItem FindItemById(Guid id)
         {
-            if (Id == id)
+            var result = base.FindItemById(id);
+
+            if (result != null)
             {
-                return this;
+                return result;
             }
 
             foreach (BaseItem item in Children)
             {
-                var folder = item as Folder;
+                result = item.FindItemById(id);
 
-                if (folder != null)
+                if (result != null)
                 {
-                    var foundItem = folder.FindById(id);
-
-                    if (foundItem != null)
-                    {
-                        return foundItem;
-                    }
-                }
-                else if (item.Id == id)
-                {
-                    return item;
+                    return result;
                 }
             }
 

+ 57 - 0
MediaBrowser.Model/Entities/IHasProviderIds.cs

@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Entities
+{
+    /// <summary>
+    /// Since BaseItem and DTOBaseItem both have ProviderIds, this interface helps avoid code repition using extension methods
+    /// </summary>
+    public interface IHasProviderIds
+    {
+        Dictionary<string, string> ProviderIds { get; set; }
+    }
+
+    public static class IProviderIdsExtensions
+    {
+        /// <summary>
+        /// Gets a provider id
+        /// </summary>
+        public static string GetProviderId(this IHasProviderIds instance, MetadataProviders provider)
+        {
+            return instance.GetProviderId(provider.ToString());
+        }
+
+        /// <summary>
+        /// Gets a provider id
+        /// </summary>
+        public static string GetProviderId(this IHasProviderIds instance, string name)
+        {
+            if (instance.ProviderIds == null)
+            {
+                return null;
+            }
+
+            return instance.ProviderIds[name];
+        }
+
+        /// <summary>
+        /// Sets a provider id
+        /// </summary>
+        public static void SetProviderId(this IHasProviderIds instance, string name, string value)
+        {
+            if (instance.ProviderIds == null)
+            {
+                instance.ProviderIds = new Dictionary<string, string>();
+            }
+
+            instance.ProviderIds[name] = value;
+        }
+
+        /// <summary>
+        /// Sets a provider id
+        /// </summary>
+        public static void SetProviderId(this IHasProviderIds instance, MetadataProviders provider, string value)
+        {
+            instance.SetProviderId(provider.ToString(), value);
+        }
+    }
+}

+ 14 - 0
MediaBrowser.Model/Entities/ItemSpecialCounts.cs

@@ -0,0 +1,14 @@
+
+namespace MediaBrowser.Model.Entities
+{
+    /// <summary>
+    /// Since it can be slow to collect this data. This class helps provide a way to calculate them all at once.
+    /// </summary>
+    public class ItemSpecialCounts
+    {
+        public int RecentlyAddedItemCount { get; set; }
+        public int RecentlyAddedUnPlayedItemCount { get; set; }
+        public int InProgressItemCount { get; set; }
+        public decimal WatchedPercentage { get; set; }
+    }
+}

+ 1 - 10
MediaBrowser.Model/Entities/Person.cs

@@ -15,20 +15,11 @@ namespace MediaBrowser.Model.Entities
     {
         public string Name { get; set; }
         public string Overview { get; set; }
-        public PersonType PersonType { get; set; }
+        public string Type { get; set; }
 
         public override string ToString()
         {
             return Name;
         }
     }
-
-    public enum PersonType
-    {
-        Other,
-        Actor,
-        Director,
-        Writer,
-        Producer
-    }
 }

+ 15 - 0
MediaBrowser.Model/Entities/User.cs

@@ -0,0 +1,15 @@
+
+namespace MediaBrowser.Model.Entities
+{
+    public class User : BaseEntity
+    {
+        public string MaxParentalRating { get; set; }
+
+        public int RecentItemDays { get; set; }
+
+        public User()
+        {
+            RecentItemDays = 14;
+        }
+    }
+}

+ 3 - 4
MediaBrowser.Model/Users/UserItemData.cs → MediaBrowser.Model/Entities/UserItemData.cs

@@ -1,13 +1,12 @@
-using MediaBrowser.Model.Entities;
-using System;
+using System;
 
-namespace MediaBrowser.Model.Users
+namespace MediaBrowser.Model.Entities
 {
     public class UserItemData
     {
         public UserItemRating Rating { get; set; }
 
-        public TimeSpan PlaybackPosition { get; set; }
+        public long PlaybackPositionTicks { get; set; }
 
         public int PlayCount { get; set; }
     }

+ 8 - 9
MediaBrowser.Model/Entities/Video.cs

@@ -6,28 +6,27 @@ namespace MediaBrowser.Model.Entities
     {
         public VideoType VideoType { get; set; }
 
-        private IEnumerable<string> _Subtitles = new string[] { };
-        public IEnumerable<string> Subtitles { get { return _Subtitles; } set { _Subtitles = value; } }
-
-        private IEnumerable<AudioStream> _AudioStreams = new AudioStream[] { };
-        public IEnumerable<AudioStream> AudioStreams { get { return _AudioStreams; } set { _AudioStreams = value; } }
+        public IEnumerable<string> Subtitles { get; set; }
+        public IEnumerable<AudioStream> AudioStreams { get; set; }
 
         public int Height { get; set; }
         public int Width { get; set; }
         public string ScanType { get; set; }
         public string FrameRate { get; set; }
-        public int VideoBitRate { get; set; }
-        public string VideoCodec { get; set; }
+        public int BitRate { get; set; }
+        public string Codec { get; set; }
     }
 
     public class AudioStream
     {
-        public string AudioFormat { get; set; }
-        public string AudioProfile { get; set; }
+        public string Format { get; set; }
+        public string Profile { get; set; }
         public string Language { get; set; }
         public int BitRate { get; set; }
         public int Channels { get; set; }
         public int SampleRate { get; set; }
+        public bool IsDefault { get; set; }
+        public bool IsForced { get; set; }
     }
 
     public enum VideoType

+ 5 - 3
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -32,7 +32,7 @@
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
-    <Compile Include="DTO\ApiBaseItem.cs" />
+    <Compile Include="DTO\DTOBaseItem.cs" />
     <Compile Include="Entities\Audio.cs" />
     <Compile Include="Entities\BaseEntity.cs" />
     <Compile Include="Entities\BaseItem.cs" />
@@ -40,6 +40,8 @@
     <Compile Include="Entities\Folder.cs" />
     <Compile Include="Entities\Genre.cs" />
     <Compile Include="Entities\ImageType.cs" />
+    <Compile Include="Entities\IHasProviderIds.cs" />
+    <Compile Include="Entities\ItemSpecialCounts.cs" />
     <Compile Include="Entities\MetadataProviders.cs" />
     <Compile Include="Entities\Person.cs" />
     <Compile Include="Entities\Studio.cs" />
@@ -49,8 +51,8 @@
     <Compile Include="DTO\PluginInfo.cs" />
     <Compile Include="Progress\TaskProgress.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="Users\User.cs" />
-    <Compile Include="Users\UserItemData.cs" />
+    <Compile Include="Entities\User.cs" />
+    <Compile Include="Entities\UserItemData.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 0 - 34
MediaBrowser.Model/Users/User.cs

@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Model.Users
-{
-    public class User : BaseEntity
-    {
-        public string MaxParentalRating { get; set; }
-
-        private Dictionary<Guid, UserItemData> _ItemData = new Dictionary<Guid, UserItemData>();
-        public Dictionary<Guid, UserItemData> ItemData { get { return _ItemData; } set { _ItemData = value; } }
-
-        public int RecentItemDays { get; set; }
-
-        public User()
-        {
-            RecentItemDays = 14;
-        }
-
-        /// <summary>
-        /// Gets user data for an item, if there is any
-        /// </summary>
-        public UserItemData GetItemData(Guid itemId)
-        {
-            if (ItemData.ContainsKey(itemId))
-            {
-                return ItemData[itemId];
-            }
-
-            return null;
-        }
-    }
-}

+ 0 - 2
MediaBrowser.Movies/Entities/Movie.cs

@@ -1,12 +1,10 @@
 using System.Collections.Generic;
-using System.Runtime.Serialization;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Movies.Entities
 {
     public class Movie : Video
     {
-        [IgnoreDataMember]
         public IEnumerable<Video> SpecialFeatures { get; set; }
     }
 }

+ 0 - 1
MediaBrowser.TV/Entities/Season.cs

@@ -9,7 +9,6 @@ namespace MediaBrowser.TV.Entities
         /// <summary>
         /// Store these to reduce disk access in Episode Resolver
         /// </summary>
-        [IgnoreDataMember]
         internal IEnumerable<string> MetadataFiles { get; set; }
     }
 }

+ 1 - 1
MediaBrowser.TV/Metadata/SeriesXmlParser.cs

@@ -71,7 +71,7 @@ namespace MediaBrowser.TV.Metadata
                             int runtime;
                             if (int.TryParse(text.Split(' ')[0], out runtime))
                             {
-                                item.RunTimeInMilliseconds = runtime * 60000;
+                                item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
                             }
                         }
                         break;