瀏覽代碼

Merge pull request #1923 from MediaBrowser/dev

Dev
Luke 9 年之前
父節點
當前提交
ecdfb3ceff
共有 25 個文件被更改,包括 389 次插入323 次删除
  1. 5 3
      MediaBrowser.Api/Images/ImageService.cs
  2. 1 2
      MediaBrowser.Api/StartupWizardService.cs
  3. 1 1
      MediaBrowser.Controller/Entities/BaseItem.cs
  4. 14 1
      MediaBrowser.Controller/Entities/Book.cs
  5. 6 1
      MediaBrowser.Controller/Entities/IHasSeries.cs
  6. 33 27
      MediaBrowser.Controller/Entities/TV/Episode.cs
  7. 13 6
      MediaBrowser.Controller/Entities/TV/Season.cs
  8. 0 6
      MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
  9. 0 1
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  10. 10 0
      MediaBrowser.Model/Dto/BaseItemDto.cs
  11. 3 1
      MediaBrowser.Model/Entities/ChapterInfo.cs
  12. 2 0
      MediaBrowser.Model/Querying/ItemFields.cs
  13. 3 1
      MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
  14. 1 5
      MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
  15. 118 157
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  16. 4 4
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  17. 27 10
      MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
  18. 0 2
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  19. 2 0
      MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
  20. 58 33
      MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
  21. 76 6
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  22. 12 1
      MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
  23. 0 5
      MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
  24. 0 5
      MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
  25. 0 45
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

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

@@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images
         {
             var list = new List<ImageInfo>();
 
-            foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type)))
+            var itemImages = item.ImageInfos;
+
+            foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type)))
             {
                 var info = GetImageInfo(item, image, null);
 
@@ -283,14 +285,14 @@ namespace MediaBrowser.Api.Images
                 }
             }
 
-            foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
+            foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
             {
                 var index = 0;
 
                 // Prevent implicitly captured closure
                 var currentImageType = imageType;
 
-                foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType))
+                foreach (var image in itemImages.Where(i => i.Type == currentImageType))
                 {
                     var info = GetImageInfo(item, image, index);
 

+ 1 - 2
MediaBrowser.Api/StartupWizardService.cs

@@ -114,11 +114,10 @@ namespace MediaBrowser.Api
         private void SetWizardFinishValues(ServerConfiguration config)
         {
             config.EnableLocalizedGuids = true;
-            config.EnableCustomPathSubFolders = true;
             config.EnableStandaloneMusicKeys = true;
             config.EnableCaseSensitiveItemIds = true;
             //config.EnableFolderView = true;
-            config.SchemaVersion = 97;
+            config.SchemaVersion = 107;
         }
 
         public void Post(UpdateStartupConfiguration request)

+ 1 - 1
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1878,7 +1878,7 @@ namespace MediaBrowser.Controller.Entities
                 return new ItemImageInfo
                 {
                     Path = path,
-                    DateModified = FileSystem.GetLastWriteTimeUtc(path),
+                    DateModified = chapter.ImageDateModified,
                     Type = imageType
                 };
             }

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

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Providers;
+using System;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using System.Linq;
 using System.Runtime.Serialization;
@@ -17,7 +18,19 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        [IgnoreDataMember]
         public string SeriesName { get; set; }
+        public Guid? SeriesId { get; set; }
+
+        public string FindSeriesName()
+        {
+            return SeriesName;
+        }
+
+        public Guid? FindSeriesId()
+        {
+            return SeriesId;
+        }
 
         public override bool CanDownload()
         {

+ 6 - 1
MediaBrowser.Controller/Entities/IHasSeries.cs

@@ -1,4 +1,6 @@
 
+using System;
+
 namespace MediaBrowser.Controller.Entities
 {
     public interface IHasSeries
@@ -7,6 +9,9 @@ namespace MediaBrowser.Controller.Entities
         /// Gets the name of the series.
         /// </summary>
         /// <value>The name of the series.</value>
-        string SeriesName { get; }
+        string SeriesName { get; set; }
+        string FindSeriesName();
+        Guid? SeriesId { get; set; }
+        Guid? FindSeriesId();
     }
 }

+ 33 - 27
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.Entities.TV
     /// </summary>
     public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
     {
-
         public Episode()
         {
             RemoteTrailers = new List<MediaUrl>();
@@ -25,11 +24,11 @@ namespace MediaBrowser.Controller.Entities.TV
         public List<Guid> RemoteTrailerIds { get; set; }
         public List<MediaUrl> RemoteTrailers { get; set; }
 
-    /// <summary>
-    /// Gets the season in which it aired.
-    /// </summary>
-    /// <value>The aired season.</value>
-    public int? AirsBeforeSeasonNumber { get; set; }
+        /// <summary>
+        /// Gets the season in which it aired.
+        /// </summary>
+        /// <value>The aired season.</value>
+        public int? AirsBeforeSeasonNumber { get; set; }
         public int? AirsAfterSeasonNumber { get; set; }
         public int? AirsBeforeEpisodeNumber { get; set; }
 
@@ -166,13 +165,27 @@ namespace MediaBrowser.Controller.Entities.TV
         }
 
         [IgnoreDataMember]
-        public string SeriesName
-        {
-            get
-            {
-                var series = Series;
-                return series == null ? null : series.Name;
-            }
+        public string SeriesName { get; set; }
+
+        [IgnoreDataMember]
+        public string SeasonName { get; set; }
+
+        public string FindSeasonName()
+        {
+            var season = Season;
+            return season == null ? SeasonName : season.Name;
+        }
+
+        public string FindSeriesName()
+        {
+            var series = Series;
+            return series == null ? SeriesName : series.Name;
+        }
+
+        public Guid? FindSeasonId()
+        {
+            var season = Season;
+            return season == null ? (Guid?)null : season.Id;
         }
 
         /// <summary>
@@ -235,20 +248,13 @@ namespace MediaBrowser.Controller.Entities.TV
         }
 
         [IgnoreDataMember]
-        public Guid? SeasonId
-        {
-            get
-            {
-                // First see if the parent is a Season
-                var season = Season;
-
-                if (season != null)
-                {
-                    return season.Id;
-                }
-
-                return null;
-            }
+        public Guid? SeasonId { get; set; }
+        public Guid? SeriesId { get; set; }
+
+        public Guid? FindSeriesId()
+        {
+            var series = Series;
+            return series == null ? (Guid?)null : series.Id;
         }
 
         public override IEnumerable<Guid> GetAncestorIds()

+ 13 - 6
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -235,13 +235,20 @@ namespace MediaBrowser.Controller.Entities.TV
         }
 
         [IgnoreDataMember]
-        public string SeriesName
+        public string SeriesName { get; set; }
+
+        public Guid? SeriesId { get; set; }
+
+        public string FindSeriesName()
         {
-            get
-            {
-                var series = Series;
-                return series == null ? null : series.Name;
-            }
+            var series = Series;
+            return series == null ? SeriesName : series.Name;
+        }
+
+        public Guid? FindSeriesId()
+        {
+            var series = Series;
+            return series == null ? (Guid?)null : series.Id;
         }
 
         /// <summary>

+ 0 - 6
MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs

@@ -49,12 +49,6 @@ namespace MediaBrowser.Model.Configuration
         /// </summary>
         /// <value>The cache path.</value>
         public string CachePath { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether [enable custom path sub folders].
-        /// </summary>
-        /// <value><c>true</c> if [enable custom path sub folders]; otherwise, <c>false</c>.</value>
-        public bool EnableCustomPathSubFolders { get; set; }
         
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.

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

@@ -214,7 +214,6 @@ namespace MediaBrowser.Model.Configuration
             Migrations = new string[] { };
             SqliteCacheSize = 0;
 
-            EnableCustomPathSubFolders = true;
             EnableLocalizedGuids = true;
             DisplaySpecialsWithinSeasons = true;
 

+ 10 - 0
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -954,6 +954,16 @@ namespace MediaBrowser.Model.Dto
             get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Thumb); }
         }
 
+        /// <summary>
+        /// Gets a value indicating whether this instance has thumb.
+        /// </summary>
+        /// <value><c>true</c> if this instance has thumb; otherwise, <c>false</c>.</value>
+        [IgnoreDataMember]
+        public bool HasBackdrop
+        {
+            get { return (BackdropImageTags != null && BackdropImageTags.Count > 0) || (ParentBackdropImageTags != null && ParentBackdropImageTags.Count > 0); }
+        }
+
         /// <summary>
         /// Gets a value indicating whether this instance has primary image.
         /// </summary>

+ 3 - 1
MediaBrowser.Model/Entities/ChapterInfo.cs

@@ -1,4 +1,5 @@
-
+using System;
+
 namespace MediaBrowser.Model.Entities
 {
     /// <summary>
@@ -23,5 +24,6 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         /// <value>The image path.</value>
         public string ImagePath { get; set; }
+        public DateTime ImageDateModified { get; set; }
     }
 }

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

@@ -197,6 +197,8 @@
         /// </summary>
         SeriesGenres,
 
+        SeriesPrimaryImage,
+
         /// <summary>
         /// The series studio
         /// </summary>

+ 3 - 1
MediaBrowser.Providers/TV/MissingEpisodeProvider.cs

@@ -429,7 +429,9 @@ namespace MediaBrowser.Providers.TV
                 IndexNumber = episodeNumber,
                 ParentIndexNumber = seasonNumber,
                 Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)),
-                IsVirtualItem = true
+                IsVirtualItem = true,
+                SeasonId = season == null ? (Guid?)null : season.Id,
+                SeriesId = series.Id
             };
 
             episode.SetParent(season);

+ 1 - 5
MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs

@@ -95,13 +95,9 @@ namespace MediaBrowser.Server.Implementations.Configuration
             {
                 metadataPath = GetInternalMetadataPath();
             }
-            else if (Configuration.EnableCustomPathSubFolders)
-            {
-                metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
-            }
             else
             {
-                metadataPath = Configuration.MetadataPath;
+                metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
             }
 
             ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;

+ 118 - 157
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -663,19 +663,12 @@ namespace MediaBrowser.Server.Implementations.Dto
             dto.GameSystem = item.GameSystemName;
         }
 
-        private List<string> GetBackdropImageTags(BaseItem item, int limit)
+        private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images)
         {
-            return GetCacheTags(item, ImageType.Backdrop, limit).ToList();
-        }
-
-        private List<string> GetScreenshotImageTags(BaseItem item, int limit)
-        {
-            var hasScreenshots = item as IHasScreenshots;
-            if (hasScreenshots == null)
-            {
-                return new List<string>();
-            }
-            return GetCacheTags(item, ImageType.Screenshot, limit).ToList();
+            return images
+                .Select(p => GetImageCacheTag(item, p))
+                .Where(i => i != null)
+                .ToList();
         }
 
         private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
@@ -850,53 +843,6 @@ namespace MediaBrowser.Server.Implementations.Dto
             }
         }
 
-        /// <summary>
-        /// If an item does not any backdrops, this can be used to find the first parent that does have one
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="owner">The owner.</param>
-        /// <returns>BaseItem.</returns>
-        private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
-        {
-            var parent = item.GetParent() ?? owner;
-
-            while (parent != null)
-            {
-                if (parent.GetImages(ImageType.Backdrop).Any())
-                {
-                    return parent;
-                }
-
-                parent = parent.GetParent();
-            }
-
-            return null;
-        }
-
-        /// <summary>
-        /// If an item does not have a logo, this can be used to find the first parent that does have one
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="type">The type.</param>
-        /// <param name="owner">The owner.</param>
-        /// <returns>BaseItem.</returns>
-        private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
-        {
-            var parent = item.GetParent() ?? owner;
-
-            while (parent != null)
-            {
-                if (parent.HasImage(type))
-                {
-                    return parent;
-                }
-
-                parent = parent.GetParent();
-            }
-
-            return null;
-        }
-
         /// <summary>
         /// Gets the chapter info dto.
         /// </summary>
@@ -917,7 +863,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                     Path = chapterInfo.ImagePath,
                     Type = ImageType.Chapter,
-                    DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath)
+                    DateModified = chapterInfo.ImageDateModified
                 });
             }
 
@@ -1027,7 +973,7 @@ namespace MediaBrowser.Server.Implementations.Dto
             var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
             if (backdropLimit > 0)
             {
-                dto.BackdropImageTags = GetBackdropImageTags(item, backdropLimit);
+                dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
             }
 
             if (fields.Contains(ItemFields.ScreenshotImageTags))
@@ -1035,7 +981,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
                 if (screenshotLimit > 0)
                 {
-                    dto.ScreenshotImageTags = GetScreenshotImageTags(item, screenshotLimit);
+                    dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
                 }
             }
 
@@ -1064,6 +1010,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             dto.Id = GetDtoId(item);
             dto.IndexNumber = item.IndexNumber;
+            dto.ParentIndexNumber = item.ParentIndexNumber;
             dto.IsFolder = item.IsFolder;
             dto.MediaType = item.MediaType;
             dto.LocationType = item.LocationType;
@@ -1076,15 +1023,11 @@ namespace MediaBrowser.Server.Implementations.Dto
             dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
             dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 
-            var hasCriticRating = item as IHasCriticRating;
-            if (hasCriticRating != null)
-            {
-                dto.CriticRating = hasCriticRating.CriticRating;
+            dto.CriticRating = item.CriticRating;
 
-                if (fields.Contains(ItemFields.CriticRatingSummary))
-                {
-                    dto.CriticRatingSummary = hasCriticRating.CriticRatingSummary;
-                }
+            if (fields.Contains(ItemFields.CriticRatingSummary))
+            {
+                dto.CriticRatingSummary = item.CriticRatingSummary;
             }
 
             var hasTrailers = item as IHasTrailers;
@@ -1127,23 +1070,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             if (fields.Contains(ItemFields.ShortOverview))
             {
-                var hasShortOverview = item as IHasShortOverview;
-                if (hasShortOverview != null)
-                {
-                    dto.ShortOverview = hasShortOverview.ShortOverview;
-                }
-            }
-
-            // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
-            if (backdropLimit > 0 && dto.BackdropImageTags.Count == 0)
-            {
-                var parentWithBackdrop = GetParentBackdropItem(item, owner);
-
-                if (parentWithBackdrop != null)
-                {
-                    dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop);
-                    dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop, backdropLimit);
-                }
+                dto.ShortOverview = item.ShortOverview;
             }
 
             if (fields.Contains(ItemFields.ParentId))
@@ -1155,46 +1082,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 }
             }
 
-            dto.ParentIndexNumber = item.ParentIndexNumber;
-
-            // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
-            if (!dto.HasLogo && options.GetImageLimit(ImageType.Logo) > 0)
-            {
-                var parentWithLogo = GetParentImageItem(item, ImageType.Logo, owner);
-
-                if (parentWithLogo != null)
-                {
-                    dto.ParentLogoItemId = GetDtoId(parentWithLogo);
-
-                    dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo);
-                }
-            }
-
-            // If there is no art, indicate what parent has one in case the Ui wants to allow inheritance
-            if (!dto.HasArtImage && options.GetImageLimit(ImageType.Art) > 0)
-            {
-                var parentWithImage = GetParentImageItem(item, ImageType.Art, owner);
-
-                if (parentWithImage != null)
-                {
-                    dto.ParentArtItemId = GetDtoId(parentWithImage);
-
-                    dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art);
-                }
-            }
-
-            // If there is no thumb, indicate what parent has one in case the Ui wants to allow inheritance
-            if (!dto.HasThumb && options.GetImageLimit(ImageType.Thumb) > 0)
-            {
-                var parentWithImage = GetParentImageItem(item, ImageType.Thumb, owner);
-
-                if (parentWithImage != null)
-                {
-                    dto.ParentThumbItemId = GetDtoId(parentWithImage);
-
-                    dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb);
-                }
-            }
+            AddInheritedImages(dto, item, options, owner);
 
             if (fields.Contains(ItemFields.Path))
             {
@@ -1426,42 +1314,38 @@ namespace MediaBrowser.Server.Implementations.Dto
                     dto.SeasonId = seasonId.Value.ToString("N");
                 }
 
-                var episodeSeason = episode.Season;
-                if (episodeSeason != null)
+                dto.SeasonName = episode.SeasonName;
+
+                var seriesId = episode.SeriesId;
+                if (seriesId.HasValue)
                 {
-                    if (fields.Contains(ItemFields.SeasonName))
-                    {
-                        dto.SeasonName = episodeSeason.Name;
-                    }
+                    dto.SeriesId = seriesId.Value.ToString("N");
                 }
 
-                var episodeSeries = episode.Series;
+                Series episodeSeries = null;
 
-                if (episodeSeries != null)
+                if (fields.Contains(ItemFields.SeriesGenres))
                 {
-                    if (fields.Contains(ItemFields.SeriesGenres))
+                    episodeSeries = episodeSeries ?? episode.Series;
+                    if (episodeSeries != null)
                     {
                         dto.SeriesGenres = episodeSeries.Genres.ToList();
                     }
+                }
 
-                    dto.SeriesId = GetDtoId(episodeSeries);
-
-                    if (fields.Contains(ItemFields.AirTime))
-                    {
-                        dto.AirTime = episodeSeries.AirTime;
-                    }
-
-                    if (options.GetImageLimit(ImageType.Thumb) > 0)
-                    {
-                        dto.SeriesThumbImageTag = GetImageCacheTag(episodeSeries, ImageType.Thumb);
-                    }
-
-                    if (options.GetImageLimit(ImageType.Primary) > 0)
+                if (fields.Contains(ItemFields.SeriesPrimaryImage))
+                {
+                    episodeSeries = episodeSeries ?? episode.Series;
+                    if (episodeSeries != null)
                     {
                         dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
                     }
+                }
 
-                    if (fields.Contains(ItemFields.SeriesStudio))
+                if (fields.Contains(ItemFields.SeriesStudio))
+                {
+                    episodeSeries = episodeSeries ?? episode.Series;
+                    if (episodeSeries != null)
                     {
                         dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
                     }
@@ -1483,16 +1367,29 @@ namespace MediaBrowser.Server.Implementations.Dto
             var season = item as Season;
             if (season != null)
             {
-                series = season.Series;
+                dto.SeriesName = season.SeriesName;
+
+                var seriesId = season.SeriesId;
+                if (seriesId.HasValue)
+                {
+                    dto.SeriesId = seriesId.Value.ToString("N");
+                }
 
-                if (series != null)
+                series = null;
+
+                if (fields.Contains(ItemFields.SeriesStudio))
                 {
-                    dto.SeriesId = GetDtoId(series);
-                    dto.SeriesName = series.Name;
-                    dto.AirTime = series.AirTime;
-                    dto.SeriesStudio = series.Studios.FirstOrDefault();
+                    series = series ?? season.Series;
+                    if (series != null)
+                    {
+                        dto.SeriesStudio = series.Studios.FirstOrDefault();
+                    }
+                }
 
-                    if (options.GetImageLimit(ImageType.Primary) > 0)
+                if (fields.Contains(ItemFields.SeriesPrimaryImage))
+                {
+                    series = series ?? season.Series;
+                    if (series != null)
                     {
                         dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
                     }
@@ -1543,6 +1440,70 @@ namespace MediaBrowser.Server.Implementations.Dto
             }
         }
 
+        private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem owner)
+        {
+            var logoLimit = options.GetImageLimit(ImageType.Logo);
+            var artLimit = options.GetImageLimit(ImageType.Art);
+            var thumbLimit = options.GetImageLimit(ImageType.Thumb);
+            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
+
+            if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
+            {
+                return;
+            }
+
+            BaseItem parent = null;
+            var isFirst = true;
+
+            while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) && 
+                (parent = parent ?? (isFirst ? item.GetParent() ?? owner : parent)) != null)
+            {
+                if (logoLimit > 0 && !dto.HasLogo && dto.ParentLogoItemId == null)
+                {
+                    var image = parent.GetImageInfo(ImageType.Logo, 0);
+
+                    if (image != null)
+                    {
+                        dto.ParentLogoItemId = GetDtoId(parent);
+                        dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
+                    }
+                }
+                if (artLimit > 0 && !dto.HasArtImage && dto.ParentArtItemId == null)
+                {
+                    var image = parent.GetImageInfo(ImageType.Art, 0);
+
+                    if (image != null)
+                    {
+                        dto.ParentArtItemId = GetDtoId(parent);
+                        dto.ParentArtImageTag = GetImageCacheTag(parent, image);
+                    }
+                }
+                if (thumbLimit > 0 && !dto.HasThumb && (dto.ParentThumbItemId == null || parent is Series))
+                {
+                    var image = parent.GetImageInfo(ImageType.Thumb, 0);
+
+                    if (image != null)
+                    {
+                        dto.ParentThumbItemId = GetDtoId(parent);
+                        dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
+                    }
+                }
+                if (backdropLimit > 0 && !dto.HasBackdrop)
+                {
+                    var images = parent.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList();
+
+                    if (images.Count > 0)
+                    {
+                        dto.ParentBackdropItemId = GetDtoId(parent);
+                        dto.ParentBackdropImageTags = GetImageTags(parent, images);
+                    }
+                }
+
+                isFirst = false;
+                parent = parent.GetParent();
+            }
+        }
+
         private string GetMappedPath(IHasMetadata item)
         {
             var path = item.Path;

+ 4 - 4
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -368,10 +368,10 @@ namespace MediaBrowser.Server.Implementations.Library
             {
                 return;
             }
-            //if (!(item is Folder))
-            //{
-            //    return;
-            //}
+            if (!(item is Folder))
+            {
+                return;
+            }
             LibraryItemsCache.AddOrUpdate(id, item, delegate { return item; });
         }
 

+ 27 - 10
MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs

@@ -30,21 +30,38 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
                 return null;
             }
 
-            var season = parent as Season;
-
-            // Just in case the user decided to nest episodes. 
-            // Not officially supported but in some cases we can handle it.
-            if (season == null)
-            {
-                season = parent.GetParents().OfType<Season>().FirstOrDefault();
-            }
-
             // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
             // Also handle flat tv folders
-            if (season != null || args.HasParent<Series>() || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
             {
                 var episode = ResolveVideo<Episode>(args, false);
 
+                if (episode != null)
+                {
+                    var season = parent as Season;
+                    // Just in case the user decided to nest episodes. 
+                    // Not officially supported but in some cases we can handle it.
+                    if (season == null)
+                    {
+                        season = parent.GetParents().OfType<Season>().FirstOrDefault();
+                    }
+
+                    var series = parent as Series;
+                    if (series == null)
+                    {
+                        series = parent.GetParents().OfType<Series>().FirstOrDefault();
+                    }
+
+                    if (series != null)
+                    {
+                        episode.SeriesId = series.Id;
+                    }
+                    if (season != null)
+                    {
+                        episode.SeasonId = season.Id;
+                    }
+                }
+
                 return episode;
             }
 

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

@@ -1214,8 +1214,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
 
                     list.Add(item);
-
-                    _libraryManager.RegisterItem(item);
                 }
                 catch (OperationCanceledException)
                 {

+ 2 - 0
MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -152,6 +152,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                             }
 
                             chapter.ImagePath = path;
+                            chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
                             changesMade = true;
                         }
                         catch (Exception ex)
@@ -170,6 +171,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase))
                 {
                     chapter.ImagePath = path;
+                    chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
                     changesMade = true;
                 }
             }

+ 58 - 33
MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs

@@ -142,52 +142,77 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
         }
 
-        private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress<double> progress)
+        private Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery
+            return UpdateToLatestSchema(0, 0, null, cancellationToken, progress);
+        }
+
+        private async Task UpdateToLatestSchema(int queryStartIndex, int progressStartIndex, int? totalRecordCount, CancellationToken cancellationToken, IProgress<double> progress)
+        {
+            IEnumerable<BaseItem> items;
+            int numItemsToSave;
+            var pageSize = 1000;
+
+            if (totalRecordCount.HasValue)
             {
-                IsCurrentSchema = false,
-                ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name }
-            });
+                var list = _libraryManager.GetItemList(new InternalItemsQuery
+                {
+                    IsCurrentSchema = false,
+                    ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name },
+                    StartIndex = queryStartIndex,
+                    Limit = pageSize
 
-            var numComplete = 0;
-            var numItems = itemIds.Count;
+                }).ToList();
+
+                items = list;
+                numItemsToSave = list.Count;
+            }
+            else
+            {
+                var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery
+                {
+                    IsCurrentSchema = false,
+                    ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name },
+                    StartIndex = queryStartIndex,
+                    Limit = pageSize
+                });
+
+                totalRecordCount = itemsResult.TotalRecordCount;
+                items = itemsResult.Items;
+                numItemsToSave = itemsResult.Items.Length;
+            }
+
+            var numItems = totalRecordCount.Value;
 
             _logger.Debug("Upgrading schema for {0} items", numItems);
 
-            foreach (var itemId in itemIds)
+            if (numItemsToSave > 0)
             {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                if (itemId != Guid.Empty)
+                try
                 {
-                    // Somehow some invalid data got into the db. It probably predates the boundary checking
-                    var item = _libraryManager.GetItemById(itemId);
-
-                    if (item != null)
-                    {
-                        try
-                        {
-                            await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false);
-                        }
-                        catch (OperationCanceledException)
-                        {
-                            throw;
-                        }
-                        catch (Exception ex)
-                        {
-                            _logger.ErrorException("Error saving item", ex);
-                        }
-                    }
+                    await _itemRepo.SaveItems(items, cancellationToken).ConfigureAwait(false);
+                }
+                catch (OperationCanceledException)
+                {
+                    throw;
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error saving item", ex);
                 }
 
-                numComplete++;
-                double percent = numComplete;
+                progressStartIndex += pageSize;
+                double percent = progressStartIndex;
                 percent /= numItems;
                 progress.Report(percent * 100);
-            }
 
-            progress.Report(100);
+                var newStartIndex = queryStartIndex + (pageSize - numItemsToSave);
+                await UpdateToLatestSchema(newStartIndex, progressStartIndex, totalRecordCount, cancellationToken, progress).ConfigureAwait(false);
+            }
+            else
+            {
+                progress.Report(100);
+            }
         }
 
         private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress<double> progress)

+ 76 - 6
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
         private IDbCommand _updateInheritedRatingCommand;
         private IDbCommand _updateInheritedTagsCommand;
 
-        public const int LatestSchemaVersion = 97;
+        public const int LatestSchemaVersion = 107;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
@@ -271,10 +271,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
             _connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text");
             _connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text");
+            _connection.AddColumn(Logger, "TypedBaseItems", "SeasonName", "Text");
+            _connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID");
+            _connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID");
 
             _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
             _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
 
+            _connection.AddColumn(Logger, ChaptersTableName, "ImageDateModified", "DATETIME");
+
             string[] postQueries =
 
                                 {
@@ -402,7 +407,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
             "Album",
             "CriticRating",
             "CriticRatingSummary",
-            "IsVirtualItem"
+            "IsVirtualItem",
+            "SeriesName",
+            "SeasonName",
+            "SeasonId",
+            "SeriesId"
         };
 
         private readonly string[] _mediaStreamSaveColumns =
@@ -522,7 +531,10 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 "Album",
                 "IsVirtualItem",
                 "SeriesName",
-                "UserDataKey"
+                "UserDataKey",
+                "SeasonName",
+                "SeasonId",
+                "SeriesId"
             };
             _saveItemCommand = _connection.CreateCommand();
             _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -581,6 +593,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@StartPositionTicks");
             _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@Name");
             _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath");
+            _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImageDateModified");
 
             // MediaStreams
             _deleteStreamsCommand = _connection.CreateCommand();
@@ -944,7 +957,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     var hasSeries = item as IHasSeries;
                     if (hasSeries != null)
                     {
-                        _saveItemCommand.GetParameter(index++).Value = hasSeries.SeriesName;
+                        _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesName();
                     }
                     else
                     {
@@ -953,6 +966,27 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
                     _saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault();
 
+                    var episode = item as Episode;
+                    if (episode != null)
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = episode.FindSeasonName();
+                        _saveItemCommand.GetParameter(index++).Value = episode.FindSeasonId();
+                    }
+                    else
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = null;
+                        _saveItemCommand.GetParameter(index++).Value = null;
+                    }
+
+                    if (hasSeries != null)
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesId();
+                    }
+                    else
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = null;
+                    }
+
                     _saveItemCommand.Transaction = transaction;
 
                     _saveItemCommand.ExecuteNonQuery();
@@ -1375,6 +1409,36 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 item.IsVirtualItem = reader.GetBoolean(58);
             }
 
+            var hasSeries = item as IHasSeries;
+            if (hasSeries != null)
+            {
+                if (!reader.IsDBNull(59))
+                {
+                    hasSeries.SeriesName = reader.GetString(59);
+                }
+            }
+
+            var episode = item as Episode;
+            if (episode != null)
+            {
+                if (!reader.IsDBNull(60))
+                {
+                    episode.SeasonName = reader.GetString(60);
+                }
+                if (!reader.IsDBNull(61))
+                {
+                    episode.SeasonId = reader.GetGuid(61);
+                }
+            }
+
+            if (hasSeries != null)
+            {
+                if (!reader.IsDBNull(62))
+                {
+                    hasSeries.SeriesId = reader.GetGuid(62);
+                }
+            }
+
             return item;
         }
 
@@ -1436,7 +1500,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
             using (var cmd = _connection.CreateCommand())
             {
-                cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
+                cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
 
                 cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
 
@@ -1469,7 +1533,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
             using (var cmd = _connection.CreateCommand())
             {
-                cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
+                cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
 
                 cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
                 cmd.Parameters.Add(cmd, "@ChapterIndex", DbType.Int32).Value = index;
@@ -1507,6 +1571,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 chapter.ImagePath = reader.GetString(2);
             }
 
+            if (!reader.IsDBNull(3))
+            {
+                chapter.ImageDateModified = reader.GetDateTime(3).ToUniversalTime();
+            }
+
             return chapter;
         }
 
@@ -1566,6 +1635,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     _saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks;
                     _saveChapterCommand.GetParameter(3).Value = chapter.Name;
                     _saveChapterCommand.GetParameter(4).Value = chapter.ImagePath;
+                    _saveChapterCommand.GetParameter(5).Value = chapter.ImageDateModified;
 
                     _saveChapterCommand.Transaction = transaction;
 

+ 12 - 1
MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs

@@ -50,6 +50,11 @@ namespace MediaBrowser.Server.Implementations.TV
                 }
             }
 
+            if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
+            {
+                limit = limit.Value + 10;
+            }
+
             var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
                 IncludeItemTypes = new[] { typeof(Series).Name },
@@ -89,6 +94,11 @@ namespace MediaBrowser.Server.Implementations.TV
                 }
             }
 
+            if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
+            {
+                limit = limit.Value + 10;
+            }
+
             var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
                 IncludeItemTypes = new[] { typeof(Series).Name },
@@ -115,7 +125,8 @@ namespace MediaBrowser.Server.Implementations.TV
                 .Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
                 .OrderByDescending(i => i.Item2)
                 .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
-                .Select(i => i.Item1);
+                .Select(i => i.Item1)
+                .Take(request.Limit ?? int.MaxValue);
         }
 
         private string GetUniqueSeriesKey(BaseItem series)

+ 0 - 5
MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs

@@ -54,11 +54,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
                     {
                         return series;
                     }
-                    var episodeSeason = episode.Season;
-                    if (episodeSeason != null)
-                    {
-                        return episodeSeason;
-                    }
 
                     return episode;
                 }

+ 0 - 5
MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs

@@ -86,11 +86,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
                     {
                         return series;
                     }
-                    var episodeSeason = episode.Season;
-                    if (episodeSeason != null)
-                    {
-                        return episodeSeason;
-                    }
 
                     return episode;
                 }

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

@@ -1043,42 +1043,6 @@
     <Content Include="dashboard-ui\userpassword.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\voice\commands\controlcommands.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\commands\disablecommands.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\commands\enablecommands.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\commands\playcommands.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\commands\searchcommands.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\commands\showcommands.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\commands\togglecommands.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\grammarprocessor.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\voicedialog.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\voice.css">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\voice.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\voice\voicecommands.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\wizardagreement.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -1674,15 +1638,6 @@
     <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <None Include="dashboard-ui\voice\grammar\en-US.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-    <None Include="dashboard-ui\voice\grammar\grammar.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-    <None Include="dashboard-ui\voice\Readme.md">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup />