From c8e67f6cb1d4a5e2afc8144656b2ffac6cb1e3ca Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 4 Jul 2016 15:30:12 -0400 Subject: [PATCH 01/75] removed custom path subfolder setting --- MediaBrowser.Api/StartupWizardService.cs | 1 - .../Configuration/BaseApplicationConfiguration.cs | 6 ------ .../Configuration/ServerConfigurationManager.cs | 6 +----- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index dfb82438e9..40bf4a2ac4 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -114,7 +114,6 @@ namespace MediaBrowser.Api private void SetWizardFinishValues(ServerConfiguration config) { config.EnableLocalizedGuids = true; - config.EnableCustomPathSubFolders = true; config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; //config.EnableFolderView = true; diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs index 2b53c66889..c4f9f206d6 100644 --- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs +++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs @@ -49,12 +49,6 @@ namespace MediaBrowser.Model.Configuration /// /// The cache path. public string CachePath { get; set; } - - /// - /// Gets or sets a value indicating whether [enable custom path sub folders]. - /// - /// true if [enable custom path sub folders]; otherwise, false. - public bool EnableCustomPathSubFolders { get; set; } /// /// Initializes a new instance of the class. diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs index 7db457c6ed..e8669bbc2c 100644 --- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/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; From 46dc02705aff7a76a622217b4cb795a7dba0bafa Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 4 Jul 2016 15:34:16 -0400 Subject: [PATCH 02/75] remove custom path subfolder option --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 081c46f0ae..58b74ba64a 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -214,7 +214,6 @@ namespace MediaBrowser.Model.Configuration Migrations = new string[] { }; SqliteCacheSize = 0; - EnableCustomPathSubFolders = true; EnableLocalizedGuids = true; DisplaySpecialsWithinSeasons = true; From 26036837dd0c865a6ac3742717dc3d77ec33cf8e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 4 Jul 2016 16:11:30 -0400 Subject: [PATCH 03/75] denormalize series fields --- MediaBrowser.Api/Images/ImageService.cs | 8 ++-- MediaBrowser.Api/StartupWizardService.cs | 2 +- MediaBrowser.Controller/Entities/Book.cs | 6 +++ .../Entities/IHasSeries.cs | 4 +- .../Entities/TV/Episode.cs | 32 +++++++++------ MediaBrowser.Controller/Entities/TV/Season.cs | 11 +++-- .../Dto/DtoService.cs | 36 ++++++----------- .../Persistence/SqliteItemRepository.cs | 40 +++++++++++++++++-- 8 files changed, 89 insertions(+), 50 deletions(-) diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index a511f8c728..a549c44bc1 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images { var list = new List(); - 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); diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 40bf4a2ac4..7cdc3b6a2e 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -117,7 +117,7 @@ namespace MediaBrowser.Api config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; //config.EnableFolderView = true; - config.SchemaVersion = 97; + config.SchemaVersion = 99; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 96fad670e1..76f54edea9 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -17,8 +17,14 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] public string SeriesName { get; set; } + public string FindSeriesName() + { + return SeriesName; + } + public override bool CanDownload() { var locationType = LocationType; diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 64c33a3766..1a262ed283 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -7,6 +7,8 @@ namespace MediaBrowser.Controller.Entities /// Gets the name of the series. /// /// The name of the series. - string SeriesName { get; } + string SeriesName { get; set; } + + string FindSeriesName(); } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 2dc459239d..d7526a535f 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -25,11 +25,11 @@ namespace MediaBrowser.Controller.Entities.TV public List RemoteTrailerIds { get; set; } public List RemoteTrailers { get; set; } - /// - /// Gets the season in which it aired. - /// - /// The aired season. - public int? AirsBeforeSeasonNumber { get; set; } + /// + /// Gets the season in which it aired. + /// + /// The aired season. + public int? AirsBeforeSeasonNumber { get; set; } public int? AirsAfterSeasonNumber { get; set; } public int? AirsBeforeEpisodeNumber { get; set; } @@ -166,13 +166,21 @@ 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; } /// diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 9a90148443..a689ca5508 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -235,13 +235,12 @@ namespace MediaBrowser.Controller.Entities.TV } [IgnoreDataMember] - public string SeriesName + public string SeriesName { get; set; } + + public string FindSeriesName() { - get - { - var series = Series; - return series == null ? null : series.Name; - } + var series = Series; + return series == null ? SeriesName : series.Name; } /// diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 8805d567ab..67ae24f3e3 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1076,15 +1076,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,11 +1123,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (fields.Contains(ItemFields.ShortOverview)) { - var hasShortOverview = item as IHasShortOverview; - if (hasShortOverview != null) - { - dto.ShortOverview = hasShortOverview.ShortOverview; - } + dto.ShortOverview = item.ShortOverview; } // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance @@ -1426,14 +1418,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.SeasonId = seasonId.Value.ToString("N"); } - var episodeSeason = episode.Season; - if (episodeSeason != null) - { - if (fields.Contains(ItemFields.SeasonName)) - { - dto.SeasonName = episodeSeason.Name; - } - } + dto.SeasonName = episode.SeasonName; var episodeSeries = episode.Series; @@ -1483,14 +1468,19 @@ namespace MediaBrowser.Server.Implementations.Dto var season = item as Season; if (season != null) { + dto.SeriesName = season.SeriesName; + series = season.Series; if (series != null) { dto.SeriesId = GetDtoId(series); - dto.SeriesName = series.Name; dto.AirTime = series.AirTime; - dto.SeriesStudio = series.Studios.FirstOrDefault(); + + if (fields.Contains(ItemFields.SeriesStudio)) + { + dto.SeriesStudio = series.Studios.FirstOrDefault(); + } if (options.GetImageLimit(ImageType.Primary) > 0) { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 5b492c240e..0416474395 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/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 = 99; /// /// Initializes a new instance of the class. @@ -271,6 +271,7 @@ 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, "UserDataKeys", "Priority", "INT"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); @@ -402,7 +403,9 @@ namespace MediaBrowser.Server.Implementations.Persistence "Album", "CriticRating", "CriticRatingSummary", - "IsVirtualItem" + "IsVirtualItem", + "SeriesName", + "SeasonName" }; private readonly string[] _mediaStreamSaveColumns = @@ -522,7 +525,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "Album", "IsVirtualItem", "SeriesName", - "UserDataKey" + "UserDataKey", + "SeasonName" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -944,7 +948,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 +957,16 @@ 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(); + } + else + { + _saveItemCommand.GetParameter(index++).Value = null; + } + _saveItemCommand.Transaction = transaction; _saveItemCommand.ExecuteNonQuery(); @@ -1375,6 +1389,24 @@ 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); + } + } + return item; } From 73e2b1f28355aafae5cc00acdb1bda881b3909a8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 5 Jul 2016 00:16:03 -0400 Subject: [PATCH 04/75] add syscall error handling --- .../Native/BaseMonoApp.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index 19ae7b4d21..4011fa3de3 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -183,6 +183,14 @@ namespace MediaBrowser.Server.Mono.Native { info.SystemArchitecture = Architecture.Arm; } + else if (System.Environment.Is64BitOperatingSystem) + { + info.SystemArchitecture = Architecture.X64; + } + else + { + info.SystemArchitecture = Architecture.X86; + } info.OperatingSystemVersionString = string.IsNullOrWhiteSpace(sysName) ? System.Environment.OSVersion.VersionString : @@ -198,14 +206,21 @@ namespace MediaBrowser.Server.Mono.Native if (_unixName == null) { var uname = new Uname(); - Utsname utsname; - var callResult = Syscall.uname(out utsname); - if (callResult == 0) + try { - uname.sysname = utsname.sysname; - uname.machine = utsname.machine; - } + Utsname utsname; + var callResult = Syscall.uname(out utsname); + if (callResult == 0) + { + uname.sysname = utsname.sysname ?? string.Empty; + uname.machine = utsname.machine ?? string.Empty; + } + } + catch (Exception ex) + { + Logger.ErrorException("Error getting unix name", ex); + } _unixName = uname; } return _unixName; From 2772d595596b3e682dbce890e53bdc3ef027df46 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 5 Jul 2016 01:40:18 -0400 Subject: [PATCH 05/75] denormalize seasonid --- MediaBrowser.Api/StartupWizardService.cs | 2 +- .../Entities/TV/Episode.cs | 23 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 10 + .../TV/MissingEpisodeProvider.cs | 3 +- .../Dto/DtoService.cs | 220 ++++++++---------- .../Library/LibraryManager.cs | 8 +- .../Library/Resolvers/TV/EpisodeResolver.cs | 8 + .../LiveTv/LiveTvManager.cs | 2 - .../Persistence/SqliteItemRepository.cs | 15 +- .../CollectionFolderImageProvider.cs | 5 - .../UserViews/DynamicImageProvider.cs | 5 - 11 files changed, 135 insertions(+), 166 deletions(-) diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 7cdc3b6a2e..8f4a62ced4 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -117,7 +117,7 @@ namespace MediaBrowser.Api config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; //config.EnableFolderView = true; - config.SchemaVersion = 99; + config.SchemaVersion = 100; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index d7526a535f..4ff1813fff 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.Entities.TV /// public class Episode : Video, IHasTrailers, IHasLookupInfo, IHasSeries { - public Episode() { RemoteTrailers = new List(); @@ -181,6 +180,12 @@ namespace MediaBrowser.Controller.Entities.TV { var series = Series; return series == null ? SeriesName : series.Name; + } + + public Guid? FindSeasonId() + { + var season = Season; + return season == null ? (Guid?)null : season.Id; } /// @@ -243,21 +248,7 @@ 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 override IEnumerable GetAncestorIds() { diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 146fcc74e8..ac7d97288c 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -954,6 +954,16 @@ namespace MediaBrowser.Model.Dto get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Thumb); } } + /// + /// Gets a value indicating whether this instance has thumb. + /// + /// true if this instance has thumb; otherwise, false. + [IgnoreDataMember] + public bool HasBackdrop + { + get { return (BackdropImageTags != null && BackdropImageTags.Count > 0) || (ParentBackdropImageTags != null && ParentBackdropImageTags.Count > 0); } + } + /// /// Gets a value indicating whether this instance has primary image. /// diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 4e2d9a8d2d..e8a0057fee 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -429,7 +429,8 @@ 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 }; episode.SetParent(season); diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 67ae24f3e3..257448941a 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -663,19 +663,12 @@ namespace MediaBrowser.Server.Implementations.Dto dto.GameSystem = item.GameSystemName; } - private List GetBackdropImageTags(BaseItem item, int limit) + private List GetImageTags(BaseItem item, List images) { - return GetCacheTags(item, ImageType.Backdrop, limit).ToList(); - } - - private List GetScreenshotImageTags(BaseItem item, int limit) - { - var hasScreenshots = item as IHasScreenshots; - if (hasScreenshots == null) - { - return new List(); - } - return GetCacheTags(item, ImageType.Screenshot, limit).ToList(); + return images + .Select(p => GetImageCacheTag(item, p)) + .Where(i => i != null) + .ToList(); } private IEnumerable GetCacheTags(BaseItem item, ImageType type, int limit) @@ -850,53 +843,6 @@ namespace MediaBrowser.Server.Implementations.Dto } } - /// - /// If an item does not any backdrops, this can be used to find the first parent that does have one - /// - /// The item. - /// The owner. - /// BaseItem. - 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; - } - - /// - /// If an item does not have a logo, this can be used to find the first parent that does have one - /// - /// The item. - /// The type. - /// The owner. - /// BaseItem. - 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; - } - /// /// Gets the chapter info dto. /// @@ -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.BackdropImageTags = 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; @@ -1126,18 +1073,6 @@ namespace MediaBrowser.Server.Implementations.Dto dto.ShortOverview = item.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); - } - } - if (fields.Contains(ItemFields.ParentId)) { var displayParentId = item.DisplayParentId; @@ -1147,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)) { @@ -1420,33 +1316,36 @@ namespace MediaBrowser.Server.Implementations.Dto dto.SeasonName = episode.SeasonName; - 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(); } + } + episodeSeries = episodeSeries ?? episode.Series; + if (episodeSeries != null) + { 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 (options.GetImageLimit(ImageType.Primary) > 0) + { + 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(); } @@ -1475,7 +1374,6 @@ namespace MediaBrowser.Server.Implementations.Dto if (series != null) { dto.SeriesId = GetDtoId(series); - dto.AirTime = series.AirTime; if (fields.Contains(ItemFields.SeriesStudio)) { @@ -1533,6 +1431,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; diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 9d66455e77..7458e7541d 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/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; }); } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 14e5e446b3..e279a978e0 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -45,6 +45,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { var episode = ResolveVideo(args, false); + if (episode != null) + { + if (season != null) + { + episode.SeasonId = season.Id; + } + } + return episode; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index f32a4b59e3..64af35a9aa 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/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) { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 0416474395..e239c3b83e 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/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 = 99; + public const int LatestSchemaVersion = 100; /// /// Initializes a new instance of the class. @@ -272,6 +272,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _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, "UserDataKeys", "Priority", "INT"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); @@ -405,7 +406,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "CriticRatingSummary", "IsVirtualItem", "SeriesName", - "SeasonName" + "SeasonName", + "SeasonId" }; private readonly string[] _mediaStreamSaveColumns = @@ -526,7 +528,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "IsVirtualItem", "SeriesName", "UserDataKey", - "SeasonName" + "SeasonName", + "SeasonId" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -961,10 +964,12 @@ namespace MediaBrowser.Server.Implementations.Persistence 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; } _saveItemCommand.Transaction = transaction; @@ -1405,6 +1410,10 @@ namespace MediaBrowser.Server.Implementations.Persistence { episode.SeasonName = reader.GetString(60); } + if (!reader.IsDBNull(61)) + { + episode.SeasonId = reader.GetGuid(61); + } } return item; diff --git a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index a66884f89d..29716d33e4 100644 --- a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/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; } diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index 161f771a91..ea4da19b20 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/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; } From 1fcbd3c6da5bd061473a3f34a6410c9bbce0fc13 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 5 Jul 2016 02:01:31 -0400 Subject: [PATCH 06/75] denormalize seriesid --- MediaBrowser.Api/StartupWizardService.cs | 2 +- MediaBrowser.Controller/Entities/Book.cs | 9 ++++- .../Entities/IHasSeries.cs | 5 ++- .../Entities/TV/Episode.cs | 9 ++++- MediaBrowser.Controller/Entities/TV/Season.cs | 8 +++++ MediaBrowser.Model/Querying/ItemFields.cs | 2 ++ .../TV/MissingEpisodeProvider.cs | 3 +- .../Dto/DtoService.cs | 35 ++++++++++++------- .../Library/Resolvers/TV/EpisodeResolver.cs | 29 +++++++++------ .../Persistence/SqliteItemRepository.cs | 26 ++++++++++++-- 10 files changed, 97 insertions(+), 31 deletions(-) diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 8f4a62ced4..a59cc6909b 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -117,7 +117,7 @@ namespace MediaBrowser.Api config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; //config.EnableFolderView = true; - config.SchemaVersion = 100; + config.SchemaVersion = 101; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 76f54edea9..34368d61f1 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/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; @@ -19,12 +20,18 @@ 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() { var locationType = LocationType; diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 1a262ed283..d4dbb8ef67 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -1,4 +1,6 @@  +using System; + namespace MediaBrowser.Controller.Entities { public interface IHasSeries @@ -8,7 +10,8 @@ namespace MediaBrowser.Controller.Entities /// /// The name of the series. string SeriesName { get; set; } - string FindSeriesName(); + Guid? SeriesId { get; set; } + Guid? FindSeriesId(); } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 4ff1813fff..b13c291d11 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -248,7 +248,14 @@ namespace MediaBrowser.Controller.Entities.TV } [IgnoreDataMember] - public Guid? SeasonId { get; set; } + 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 GetAncestorIds() { diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index a689ca5508..218d0fef83 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -237,12 +237,20 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public string SeriesName { get; set; } + public Guid? SeriesId { get; set; } + public string FindSeriesName() { var series = Series; return series == null ? SeriesName : series.Name; } + public Guid? FindSeriesId() + { + var series = Series; + return series == null ? (Guid?)null : series.Id; + } + /// /// Gets the lookup information. /// diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index cea638a395..21f87247ab 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -197,6 +197,8 @@ /// SeriesGenres, + SeriesPrimaryImage, + /// /// The series studio /// diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index e8a0057fee..22e1267952 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -430,7 +430,8 @@ namespace MediaBrowser.Providers.TV ParentIndexNumber = seasonNumber, Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)), IsVirtualItem = true, - SeasonId = season == null ? (Guid?)null : season.Id + SeasonId = season == null ? (Guid?)null : season.Id, + SeriesId = series.Id }; episode.SetParent(season); diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 257448941a..925afa4286 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1316,6 +1316,12 @@ namespace MediaBrowser.Server.Implementations.Dto dto.SeasonName = episode.SeasonName; + var seriesId = episode.SeriesId; + if (seriesId.HasValue) + { + dto.SeriesId = seriesId.Value.ToString("N"); + } + Series episodeSeries = null; if (fields.Contains(ItemFields.SeriesGenres)) @@ -1327,13 +1333,7 @@ namespace MediaBrowser.Server.Implementations.Dto } } - episodeSeries = episodeSeries ?? episode.Series; - if (episodeSeries != null) - { - dto.SeriesId = GetDtoId(episodeSeries); - } - - if (options.GetImageLimit(ImageType.Primary) > 0) + if (fields.Contains(ItemFields.SeriesPrimaryImage)) { episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) @@ -1369,18 +1369,27 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.SeriesName = season.SeriesName; - series = season.Series; - - if (series != null) + var seriesId = season.SeriesId; + if (seriesId.HasValue) { - dto.SeriesId = GetDtoId(series); + dto.SeriesId = seriesId.Value.ToString("N"); + } - if (fields.Contains(ItemFields.SeriesStudio)) + series = null; + + if (fields.Contains(ItemFields.SeriesStudio)) + { + 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); } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index e279a978e0..6d0f4ffe29 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -30,23 +30,32 @@ 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().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() || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { var episode = ResolveVideo(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().FirstOrDefault(); + } + + var series = parent as Series; + if (series == null) + { + series = parent.GetParents().OfType().FirstOrDefault(); + } + + if (series != null) + { + episode.SeriesId = series.Id; + } if (season != null) { episode.SeasonId = season.Id; diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index e239c3b83e..4a70ddc2ef 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/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 = 100; + public const int LatestSchemaVersion = 101; /// /// Initializes a new instance of the class. @@ -273,6 +273,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _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"); @@ -407,7 +408,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "IsVirtualItem", "SeriesName", "SeasonName", - "SeasonId" + "SeasonId", + "SeriesId" }; private readonly string[] _mediaStreamSaveColumns = @@ -529,7 +531,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "SeriesName", "UserDataKey", "SeasonName", - "SeasonId" + "SeasonId", + "SeriesId" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -972,6 +975,15 @@ namespace MediaBrowser.Server.Implementations.Persistence _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(); @@ -1416,6 +1428,14 @@ namespace MediaBrowser.Server.Implementations.Persistence } } + if (hasSeries != null) + { + if (!reader.IsDBNull(62)) + { + hasSeries.SeriesId = reader.GetGuid(62); + } + } + return item; } From 2bfd6d3be0b2f6e9b2c0230b02bcbbe6e8984570 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 5 Jul 2016 02:05:43 -0400 Subject: [PATCH 07/75] fix backdrop images --- MediaBrowser.Server.Implementations/Dto/DtoService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 925afa4286..8bd1e5e629 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -981,7 +981,7 @@ namespace MediaBrowser.Server.Implementations.Dto var screenshotLimit = options.GetImageLimit(ImageType.Screenshot); if (screenshotLimit > 0) { - dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList()); + dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList()); } } From e50fbdfafce5974b0ff9b798988e17ed31fdf66b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 5 Jul 2016 02:09:11 -0400 Subject: [PATCH 08/75] update next up limit --- .../TV/TVSeriesManager.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs index d51f61b9a3..84d85d6675 100644 --- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs +++ b/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) From 41fd919629dc833fcf0018bc0260443d5ca2dedc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 5 Jul 2016 12:58:44 -0400 Subject: [PATCH 09/75] restore version --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 9b08f21287..bd39b81319 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.0.5984")] +[assembly: AssemblyVersion("3.1.56")] From 8629d509e4cb8d803fe1195c2861b98868854e51 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 6 Jul 2016 01:13:26 -0400 Subject: [PATCH 10/75] speed up db upgrade --- MediaBrowser.Api/StartupWizardService.cs | 2 +- .../Persistence/CleanDatabaseScheduledTask.cs | 93 ++++++++++++------- .../Persistence/SqliteItemRepository.cs | 2 +- 3 files changed, 61 insertions(+), 36 deletions(-) diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index a59cc6909b..87562b1266 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -117,7 +117,7 @@ namespace MediaBrowser.Api config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; //config.EnableFolderView = true; - config.SchemaVersion = 101; + config.SchemaVersion = 107; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index b11a3e4968..4235f6073b 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -142,52 +142,77 @@ namespace MediaBrowser.Server.Implementations.Persistence } } - private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress progress) + private Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress progress) { - var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery - { - IsCurrentSchema = false, - ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name } - }); + return UpdateToLatestSchema(0, 0, null, cancellationToken, progress); + } - var numComplete = 0; - var numItems = itemIds.Count; + private async Task UpdateToLatestSchema(int queryStartIndex, int progressStartIndex, int? totalRecordCount, CancellationToken cancellationToken, IProgress progress) + { + IEnumerable items; + int numItemsToSave; + var pageSize = 2000; + + if (totalRecordCount.HasValue) + { + var list = _libraryManager.GetItemList(new InternalItemsQuery + { + IsCurrentSchema = false, + ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name }, + StartIndex = queryStartIndex, + Limit = pageSize + + }).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 progress) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 4a70ddc2ef..31388661ba 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/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 = 101; + public const int LatestSchemaVersion = 107; /// /// Initializes a new instance of the class. From 3c6797678bd11f00182da6e2cb3dbb3cbfee628f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 6 Jul 2016 13:44:44 -0400 Subject: [PATCH 11/75] store chapter image date modified --- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Model/Entities/ChapterInfo.cs | 4 +++- .../Dto/DtoService.cs | 2 +- .../MediaEncoder/EncodingManager.cs | 2 ++ .../Persistence/CleanDatabaseScheduledTask.cs | 2 +- .../Persistence/SqliteItemRepository.cs | 13 +++++++++++-- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 9d1a45689f..0860cb61cb 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/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 }; } diff --git a/MediaBrowser.Model/Entities/ChapterInfo.cs b/MediaBrowser.Model/Entities/ChapterInfo.cs index 9da7a9caab..7e57009652 100644 --- a/MediaBrowser.Model/Entities/ChapterInfo.cs +++ b/MediaBrowser.Model/Entities/ChapterInfo.cs @@ -1,4 +1,5 @@ - +using System; + namespace MediaBrowser.Model.Entities { /// @@ -23,5 +24,6 @@ namespace MediaBrowser.Model.Entities /// /// The image path. public string ImagePath { get; set; } + public DateTime ImageDateModified { get; set; } } } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 8bd1e5e629..3e009d210a 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -863,7 +863,7 @@ namespace MediaBrowser.Server.Implementations.Dto { Path = chapterInfo.ImagePath, Type = ImageType.Chapter, - DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath) + DateModified = chapterInfo.ImageDateModified }); } diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index b1b2072c44..7f709d084f 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/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; } } diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index 4235f6073b..c321e5f015 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -151,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { IEnumerable items; int numItemsToSave; - var pageSize = 2000; + var pageSize = 1000; if (totalRecordCount.HasValue) { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 31388661ba..93f58b3994 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -278,6 +278,8 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); + _connection.AddColumn(Logger, ChaptersTableName, "ImageDateModified", "DATETIME"); + string[] postQueries = { @@ -591,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(); @@ -1497,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; @@ -1530,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; @@ -1568,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; } @@ -1627,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; From 80688496e8ad610b288ff8420a8853e91cd59749 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 6 Jul 2016 15:25:58 -0400 Subject: [PATCH 12/75] use shared voice components --- .../MediaBrowser.WebDashboard.csproj | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 9c786dbae5..bc068d3583 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -1043,42 +1043,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -1674,15 +1638,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - From bdca2ea839b0e88934bd84a3caddb9d36031f6b0 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 6 Jul 2016 15:47:17 -0400 Subject: [PATCH 13/75] 3.1.57 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index bd39b81319..39012943c4 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.56")] +[assembly: AssemblyVersion("3.1.57")] From f618650fbd4d97846322b677dcbaecc8dc41c405 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Jul 2016 11:55:39 -0400 Subject: [PATCH 14/75] revert clean task changes --- .../Persistence/CleanDatabaseScheduledTask.cs | 91 +++++++------------ 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index c321e5f015..b11a3e4968 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -142,77 +142,52 @@ namespace MediaBrowser.Server.Implementations.Persistence } } - private Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress progress) + private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress progress) { - return UpdateToLatestSchema(0, 0, null, cancellationToken, progress); - } - - private async Task UpdateToLatestSchema(int queryStartIndex, int progressStartIndex, int? totalRecordCount, CancellationToken cancellationToken, IProgress progress) - { - IEnumerable items; - int numItemsToSave; - var pageSize = 1000; - - if (totalRecordCount.HasValue) + var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery { - var list = _libraryManager.GetItemList(new InternalItemsQuery - { - IsCurrentSchema = false, - ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name }, - StartIndex = queryStartIndex, - Limit = pageSize + IsCurrentSchema = false, + ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name } + }); - }).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; + var numComplete = 0; + var numItems = itemIds.Count; _logger.Debug("Upgrading schema for {0} items", numItems); - if (numItemsToSave > 0) + foreach (var itemId in itemIds) { - try + cancellationToken.ThrowIfCancellationRequested(); + + if (itemId != Guid.Empty) { - await _itemRepo.SaveItems(items, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error saving item", ex); + // 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); + } + } } - progressStartIndex += pageSize; - double percent = progressStartIndex; + numComplete++; + double percent = numComplete; percent /= numItems; progress.Report(percent * 100); + } - var newStartIndex = queryStartIndex + (pageSize - numItemsToSave); - await UpdateToLatestSchema(newStartIndex, progressStartIndex, totalRecordCount, cancellationToken, progress).ConfigureAwait(false); - } - else - { - progress.Report(100); - } + progress.Report(100); } private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress progress) From 88be568b401c358f3ab2596ed3ccf25c694e52e0 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Jul 2016 11:56:09 -0400 Subject: [PATCH 15/75] update subtitle extraction --- .../Subtitles/SubtitleEncoder.cs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 9a83aba88b..a63aca11b3 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -120,6 +120,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles bool preserveOriginalTimestamps, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(itemId)) + { + throw new ArgumentNullException("itemId"); + } + if (string.IsNullOrWhiteSpace(mediaSourceId)) + { + throw new ArgumentNullException("mediaSourceId"); + } + var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken) .ConfigureAwait(false); @@ -141,10 +150,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles int subtitleStreamIndex, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(itemId)) + { + throw new ArgumentNullException("itemId"); + } + if (string.IsNullOrWhiteSpace(mediaSourceId)) + { + throw new ArgumentNullException("mediaSourceId"); + } + var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false); var mediaSource = mediaSources - .First(i => string.Equals(i.Id, mediaSourceId)); + .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); var subtitleStream = mediaSource.MediaStreams .First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex); @@ -609,7 +627,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw; } - process.StandardError.BaseStream.CopyToAsync(logFileStream); + // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback + Task.Run(() => StartStreamingLog(process.StandardError.BaseStream, logFileStream)); var ranToCompletion = process.WaitForExit(300000); @@ -686,6 +705,33 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } + private async Task StartStreamingLog(Stream source, Stream target) + { + try + { + using (var reader = new StreamReader(source)) + { + while (!reader.EndOfStream) + { + var line = await reader.ReadLineAsync().ConfigureAwait(false); + + var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); + + await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await target.FlushAsync().ConfigureAwait(false); + } + } + } + catch (ObjectDisposedException) + { + // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux + } + catch (Exception ex) + { + _logger.ErrorException("Error reading ffmpeg log", ex); + } + } + /// /// Sets the ass font. /// From 4283a5e854e974fe8c5a39b88331e4d71ca10e31 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Jul 2016 12:02:00 -0400 Subject: [PATCH 16/75] 3.1.58 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 39012943c4..9bebecd53a 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.57")] +[assembly: AssemblyVersion("3.1.58")] From 42b6e88aa7337a82a08e699c6e63ca09972d9ef6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Jul 2016 23:21:06 -0400 Subject: [PATCH 17/75] fix validation logging --- .../Encoder/EncoderValidator.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 859452362a..0866bd255a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -86,20 +86,28 @@ namespace MediaBrowser.MediaEncoding.Encoder "libvorbis", "srt", "h264_nvenc", - "h264_qsv" + "h264_qsv", + "ac3" }; output = output ?? string.Empty; + var index = 0; + foreach (var codec in required) { var srch = " " + codec + " "; if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1) { - _logger.Info("Encoder available: " + codec); + if (index < required.Length - 1) + { + _logger.Info("Encoder available: " + codec); + } + found.Add(codec); } + index++; } return found; From 674b40af03ba2e524c6b813f3d728047e2ba29b0 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Jul 2016 23:21:35 -0400 Subject: [PATCH 18/75] move call to create index --- .../Persistence/SqliteItemRepository.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 93f58b3994..a3217e3ed3 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -156,8 +156,6 @@ namespace MediaBrowser.Server.Implementations.Persistence string[] queries = { "create table if not exists TypedBaseItems (guid GUID primary key, type TEXT, data BLOB, ParentId GUID, Path TEXT)", - "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", - "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", "create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))", "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", @@ -303,6 +301,9 @@ namespace MediaBrowser.Server.Implementations.Persistence "drop index if exists idx_ItemValues4", "drop index if exists idx_ItemValues5", + "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", + "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", + "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", //"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)", From f952ac0f1f84f43f68cd28173b7c3ff9369a2040 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Jul 2016 23:22:02 -0400 Subject: [PATCH 19/75] fix season ids --- MediaBrowser.Controller/Entities/Book.cs | 1 + .../Entities/TV/Episode.cs | 1 + MediaBrowser.Controller/Entities/TV/Season.cs | 1 + .../Dto/DtoService.cs | 26 +++++----- .../Persistence/CleanDatabaseScheduledTask.cs | 49 ++++++++++++++----- 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 34368d61f1..9d35c86d25 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public string SeriesName { get; set; } + [IgnoreDataMember] public Guid? SeriesId { get; set; } public string FindSeriesName() diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index b13c291d11..78c35ad484 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -249,6 +249,7 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public Guid? SeasonId { get; set; } + [IgnoreDataMember] public Guid? SeriesId { get; set; } public Guid? FindSeriesId() diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 218d0fef83..865cadeb90 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -237,6 +237,7 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public string SeriesName { get; set; } + [IgnoreDataMember] public Guid? SeriesId { get; set; } public string FindSeriesName() diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 3e009d210a..de6c23cac6 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -671,17 +671,6 @@ namespace MediaBrowser.Server.Implementations.Dto .ToList(); } - private IEnumerable GetCacheTags(BaseItem item, ImageType type, int limit) - { - return item.GetImages(type) - // Convert to a list now in case GetImageCacheTag is slow - .ToList() - .Select(p => GetImageCacheTag(item, p)) - .Where(i => i != null) - .Take(limit) - .ToList(); - } - private string GetImageCacheTag(BaseItem item, ImageType type) { try @@ -1458,9 +1447,16 @@ namespace MediaBrowser.Server.Implementations.Dto 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 (parent == null) + { + break; + } + + var allImages = parent.ImageInfos; + if (logoLimit > 0 && !dto.HasLogo && dto.ParentLogoItemId == null) { - var image = parent.GetImageInfo(ImageType.Logo, 0); + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo); if (image != null) { @@ -1470,7 +1466,7 @@ namespace MediaBrowser.Server.Implementations.Dto } if (artLimit > 0 && !dto.HasArtImage && dto.ParentArtItemId == null) { - var image = parent.GetImageInfo(ImageType.Art, 0); + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art); if (image != null) { @@ -1480,7 +1476,7 @@ namespace MediaBrowser.Server.Implementations.Dto } if (thumbLimit > 0 && !dto.HasThumb && (dto.ParentThumbItemId == null || parent is Series)) { - var image = parent.GetImageInfo(ImageType.Thumb, 0); + var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb); if (image != null) { @@ -1490,7 +1486,7 @@ namespace MediaBrowser.Server.Implementations.Dto } if (backdropLimit > 0 && !dto.HasBackdrop) { - var images = parent.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList(); + var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList(); if (images.Count > 0) { diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index b11a3e4968..bf2afb5ace 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -155,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Persistence _logger.Debug("Upgrading schema for {0} items", numItems); + var list = new List(); + foreach (var itemId in itemIds) { cancellationToken.ThrowIfCancellationRequested(); @@ -166,27 +168,50 @@ namespace MediaBrowser.Server.Implementations.Persistence if (item != null) { - try - { - await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error saving item", ex); - } + list.Add(item); } } + if (list.Count >= 1000) + { + try + { + await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error saving item", ex); + } + + list.Clear(); + } + numComplete++; double percent = numComplete; percent /= numItems; progress.Report(percent * 100); } + if (list.Count > 0) + { + try + { + await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error saving item", ex); + } + } + progress.Report(100); } From 9fdc9faba24aeb7f62769cb54b348d29d5fc67cd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Jul 2016 23:22:16 -0400 Subject: [PATCH 20/75] handle HDHR's with older firmware --- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index a3e5589e88..69b6fb5a94 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -17,6 +17,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Net; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -106,18 +107,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun private async Task GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken) { - using (var stream = await _httpClient.Get(new HttpRequestOptions() + try { - Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), - CancellationToken = cancellationToken, - CacheLength = TimeSpan.FromDays(1), - CacheMode = CacheMode.Unconditional, - TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds) - })) - { - var response = JsonSerializer.DeserializeFromStream(stream); + using (var stream = await _httpClient.Get(new HttpRequestOptions() + { + Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), + CancellationToken = cancellationToken, + CacheLength = TimeSpan.FromDays(1), + CacheMode = CacheMode.Unconditional, + TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds) + })) + { + var response = JsonSerializer.DeserializeFromStream(stream); - return response.ModelNumber; + return response.ModelNumber; + } + } + catch (HttpException ex) + { + if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound) + { + // HDHR4 doesn't have this api + return "HDHR"; + } + + throw; } } @@ -455,16 +469,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun return; } - // Test it by pulling down the lineup - using (var stream = await _httpClient.Get(new HttpRequestOptions + try { - Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), - CancellationToken = CancellationToken.None - })) - { - var response = JsonSerializer.DeserializeFromStream(stream); + // Test it by pulling down the lineup + using (var stream = await _httpClient.Get(new HttpRequestOptions + { + Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), + CancellationToken = CancellationToken.None + })) + { + var response = JsonSerializer.DeserializeFromStream(stream); - info.DeviceId = response.DeviceID; + info.DeviceId = response.DeviceID; + } + } + catch (HttpException ex) + { + if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound) + { + // HDHR4 doesn't have this api + return; + } + + throw; } } From 6c790b8e2f66dc71e4ceb58817b2c601b9ba8c8e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Jul 2016 23:44:30 -0400 Subject: [PATCH 21/75] update mac icon --- MediaBrowser.Server.Mac/statusicon.png | Bin 937 -> 922 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/MediaBrowser.Server.Mac/statusicon.png b/MediaBrowser.Server.Mac/statusicon.png index 9b2cdd8a0da766fd475aebb0c4faae38814d9ba8..8f12695059a8fc1573f7b8c5be6afb747521150d 100644 GIT binary patch literal 922 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|#^NA%Cx&(BWL^R}oCO|{#S9GG z!XV7ZFl&wkP(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1agPZ!4!3&GyW z-hNDu0&UeUB}pnW4bD3Rg~O#n!lX))OPu#`K2Lb<c7ohVo(U<(blK9Bw7d3j3LkXV5qR=5 z(k00`;ib-9ANyxwPxl7wjOokXI*aq6ppoG<{Y5GU_gP-EoI2j*Tevsw{Wl@+BsI6P zX{KMJh5NJvL*7;CePdy7(&%C0lZi&%5ZoO&Q=9CicRWd1d+gKfqKACnWdl{O< zUVZwxlBt8?_dC{f>zcnFZ@8=uc^}#J%V_rAsa{LpE6v$0ChB-_vq;k#xueYv6G{cz z64&3&+nuyg!eq8@>mJ2Ej^-#)|0b!;-{w9ra9exggqpQ&M@PhMNslx>>6ctgm;?wG7+1~kFNbC6#VHTh5ID`4}&D&X^4CCqQ=d#Wzp$PzkHB@K- delta 901 zcmV;01A6?L2dM{;F@FSSK}|sb0I`n?{9y$E000SaNLh0L01FZT01FZU(%pXi0000Q zbVXQnQ*UN;cVTj608n9RZgehAMN}YmGcGkQF)>H;LN@>a0~kp}K~y+T-I7U9Qvno( zUwr%sp&$;37{O>zTCt#DkwGlt3__uxlo<FgU&W0IJ*gWA=#1w!Qj&Z&lw zryD_z<|$IDhhN)kD`D}fZK?+&5Lb1eP~D9}O%EiRUX6>P->WJ;fWVUN}`rv)?8h>`4ssc0qCU(ZFv5lv~*-A5V z6y3O>w!l387A1yp$XX^))?$V6`5a7>?@;u363KO=6tN{-Vw2tKgl9w%ydtG==Stxg zQ3j8r75KKe1Z~S0Znr;!-t-&|JyU43%s^821l1iAP?{#8?VCls+CmWVG?+z28NP=CLj$7aIS&>k;TwP3 zg?`Pq4UE3T;Mf!f$EWGcVDQ;2hJMBRGo{*gxP_iyvVYS`X!#Ul7Z1+66R>H28nlKk zSjK13KQ@D$k~?tSm+?D+8Gje8-f?`y(I<+cms0dX%9))&`5Zn+d4^?CF8+c)GjFf` be-OlX$oS`Z(o~^T00000NkvXXu0mjfmkFTA From a23144726ea762b7ed5ebe28b20db7450ea28cdc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 8 Jul 2016 00:00:37 -0400 Subject: [PATCH 22/75] add startup null check --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 383e937583..897684b732 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -152,7 +152,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { var directory = Path.GetDirectoryName(FFMpegPath); - if (FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory)) + if (!string.IsNullOrWhiteSpace(directory) && FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory)) { await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, FileSystem).DownloadFonts(directory).ConfigureAwait(false); From e9885887453dae7a38c4a5a0b0f40c680907547b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 8 Jul 2016 14:10:38 -0400 Subject: [PATCH 23/75] fix saving of country code in nfo --- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 42f0a3364f..954fb2a474 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -99,7 +99,9 @@ namespace MediaBrowser.XbmcMetadata.Savers "collectionitem", "isuserfavorite", - "userrating" + "userrating", + + "countrycode" }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); From 671a5126996e454125abb16cee56607bdd94be9f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 8 Jul 2016 14:11:13 -0400 Subject: [PATCH 24/75] add null checks to get theme media --- MediaBrowser.Api/Library/LibraryService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 4cd6a66efb..1949dea123 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -839,6 +839,7 @@ namespace MediaBrowser.Api.Library var dtoOptions = GetDtoOptions(request); var dtos = GetThemeSongIds(item).Select(_libraryManager.GetItemById) + .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); @@ -882,6 +883,7 @@ namespace MediaBrowser.Api.Library var dtoOptions = GetDtoOptions(request); var dtos = GetThemeVideoIds(item).Select(_libraryManager.GetItemById) + .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); From afaf7f7c5b39705d810b3e85b6f23a7d72f7106d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 8 Jul 2016 14:11:23 -0400 Subject: [PATCH 25/75] sanitize headers --- .../SocketSharp/WebSocketSharpRequest.cs | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index c7d889505b..7a4e4a84e6 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -134,12 +134,89 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp get { return remoteIp ?? - (remoteIp = XForwardedFor ?? - (NormalizeIp(XRealIp) ?? + (remoteIp = (CheckBadChars(XForwardedFor)) ?? + (NormalizeIp(CheckBadChars(XRealIp)) ?? (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); } } + private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; + + // + // CheckBadChars - throws on invalid chars to be not found in header name/value + // + internal static string CheckBadChars(string name) + { + if (name == null || name.Length == 0) + { + return name; + } + + // VALUE check + //Trim spaces from both ends + name = name.Trim(HttpTrimCharacters); + + //First, check for correctly formed multi-line value + //Second, check for absenece of CTL characters + int crlf = 0; + for (int i = 0; i < name.Length; ++i) + { + char c = (char)(0x000000ff & (uint)name[i]); + switch (crlf) + { + case 0: + if (c == '\r') + { + crlf = 1; + } + else if (c == '\n') + { + // Technically this is bad HTTP. But it would be a breaking change to throw here. + // Is there an exploit? + crlf = 2; + } + else if (c == 127 || (c < ' ' && c != '\t')) + { + throw new ArgumentException("net_WebHeaderInvalidControlChars"); + } + break; + + case 1: + if (c == '\n') + { + crlf = 2; + break; + } + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + + case 2: + if (c == ' ' || c == '\t') + { + crlf = 0; + break; + } + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + } + if (crlf != 0) + { + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + return name; + } + + internal static bool ContainsNonAsciiChars(string token) + { + for (int i = 0; i < token.Length; ++i) + { + if ((token[i] < 0x20) || (token[i] > 0x7e)) + { + return true; + } + } + return false; + } + private string NormalizeIp(string ip) { if (!string.IsNullOrWhiteSpace(ip)) From 3984f27027dbb94f4492850a948ca93cbe257f75 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 8 Jul 2016 14:15:10 -0400 Subject: [PATCH 26/75] 3.1.59 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 9bebecd53a..0822195880 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.58")] +[assembly: AssemblyVersion("3.1.59")] From 959c6a397cf9bb7c5494e8bb04d9b8f7dff40383 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 9 Jul 2016 13:39:04 -0400 Subject: [PATCH 27/75] add new streambuilder options --- MediaBrowser.Model/Dlna/AudioOptions.cs | 6 + MediaBrowser.Model/Dlna/CodecProfile.cs | 3 + MediaBrowser.Model/Dlna/StreamBuilder.cs | 158 +++++++++++++++--- .../TV/DummySeasonProvider.cs | 3 +- .../Library/LibraryManager.cs | 51 +++--- .../Library/Resolvers/TV/EpisodeResolver.cs | 20 ++- .../Library/Resolvers/TV/SeasonResolver.cs | 6 +- 7 files changed, 190 insertions(+), 57 deletions(-) diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs index 6ad4fa2659..162b88c981 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/AudioOptions.cs @@ -11,8 +11,14 @@ namespace MediaBrowser.Model.Dlna public AudioOptions() { Context = EncodingContext.Streaming; + + EnableDirectPlay = true; + EnableDirectStream = true; } + public bool EnableDirectPlay { get; set; } + public bool EnableDirectStream { get; set; } + public string ItemId { get; set; } public List MediaSources { get; set; } public DeviceProfile Profile { get; set; } diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs index 7200f648cd..385e98f619 100644 --- a/MediaBrowser.Model/Dlna/CodecProfile.cs +++ b/MediaBrowser.Model/Dlna/CodecProfile.cs @@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Dlna public ProfileCondition[] Conditions { get; set; } + public ProfileCondition[] ApplyConditions { get; set; } + [XmlAttribute("codec")] public string Codec { get; set; } @@ -20,6 +22,7 @@ namespace MediaBrowser.Model.Dlna public CodecProfile() { Conditions = new ProfileCondition[] {}; + ApplyConditions = new ProfileCondition[] { }; } public List GetCodecs() diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 41efa51b98..e07e56d397 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -131,6 +131,11 @@ namespace MediaBrowser.Model.Dlna List directPlayMethods = GetAudioDirectPlayMethods(item, audioStream, options); + ConditionProcessor conditionProcessor = new ConditionProcessor(); + + int? inputAudioChannels = audioStream == null ? null : audioStream.Channels; + int? inputAudioBitrate = audioStream == null ? null : audioStream.BitDepth; + if (directPlayMethods.Count > 0) { string audioCodec = audioStream == null ? null : audioStream.Codec; @@ -138,27 +143,36 @@ namespace MediaBrowser.Model.Dlna // Make sure audio codec profiles are satisfied if (!string.IsNullOrEmpty(audioCodec)) { - ConditionProcessor conditionProcessor = new ConditionProcessor(); - List conditions = new List(); foreach (CodecProfile i in options.Profile.CodecProfiles) { if (i.Type == CodecType.Audio && i.ContainsCodec(audioCodec, item.Container)) { - foreach (ProfileCondition c in i.Conditions) + bool applyConditions = true; + foreach (ProfileCondition applyCondition in i.ApplyConditions) { - conditions.Add(c); + if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate)) + { + LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item); + applyConditions = false; + break; + } + } + + if (applyConditions) + { + foreach (ProfileCondition c in i.Conditions) + { + conditions.Add(c); + } } } } - int? audioChannels = audioStream.Channels; - int? audioBitrate = audioStream.BitRate; - bool all = true; foreach (ProfileCondition c in conditions) { - if (!conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate)) + if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate)) { LogConditionFailure(options.Profile, "AudioCodecProfile", c, item); all = false; @@ -241,9 +255,23 @@ namespace MediaBrowser.Model.Dlna List audioTranscodingConditions = new List(); foreach (CodecProfile i in audioCodecProfiles) { - foreach (ProfileCondition c in i.Conditions) + bool applyConditions = true; + foreach (ProfileCondition applyCondition in i.ApplyConditions) { - audioTranscodingConditions.Add(c); + if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate)) + { + LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item); + applyConditions = false; + break; + } + } + + if (applyConditions) + { + foreach (ProfileCondition c in i.Conditions) + { + audioTranscodingConditions.Add(c); + } } } @@ -294,7 +322,7 @@ namespace MediaBrowser.Model.Dlna if (directPlayProfile != null) { // While options takes the network and other factors into account. Only applies to direct stream - if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate())) + if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()) && options.EnableDirectStream) { playMethods.Add(PlayMethod.DirectStream); } @@ -302,7 +330,7 @@ namespace MediaBrowser.Model.Dlna // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play if (item.SupportsDirectPlay && - IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options))) + IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)) && options.EnableDirectPlay) { playMethods.Add(PlayMethod.DirectPlay); } @@ -385,8 +413,8 @@ namespace MediaBrowser.Model.Dlna MediaStream videoStream = item.VideoStream; // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough - bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay); - bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream); + bool isEligibleForDirectPlay = options.EnableDirectPlay && IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay); + bool isEligibleForDirectStream = options.EnableDirectStream && IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream); _logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", options.Profile.Name ?? "Unknown Profile", @@ -463,17 +491,37 @@ namespace MediaBrowser.Model.Dlna } playlistItem.SubProtocol = transcodingProfile.Protocol; playlistItem.AudioStreamIndex = audioStreamIndex; + ConditionProcessor conditionProcessor = new ConditionProcessor(); List videoTranscodingConditions = new List(); foreach (CodecProfile i in options.Profile.CodecProfiles) { if (i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec, transcodingProfile.Container)) { - foreach (ProfileCondition c in i.Conditions) + bool applyConditions = true; + foreach (ProfileCondition applyCondition in i.ApplyConditions) { - videoTranscodingConditions.Add(c); + bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream); + int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate; + int? audioChannels = audioStream == null ? null : audioStream.Channels; + string audioProfile = audioStream == null ? null : audioStream.Profile; + + if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, audioProfile, isSecondaryAudio)) + { + LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item); + applyConditions = false; + break; + } + } + + if (applyConditions) + { + foreach (ProfileCondition c in i.Conditions) + { + videoTranscodingConditions.Add(c); + } + break; } - break; } } ApplyTranscodingConditions(playlistItem, videoTranscodingConditions); @@ -483,11 +531,42 @@ namespace MediaBrowser.Model.Dlna { if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.TargetAudioCodec, transcodingProfile.Container)) { - foreach (ProfileCondition c in i.Conditions) + bool applyConditions = true; + foreach (ProfileCondition applyCondition in i.ApplyConditions) { - audioTranscodingConditions.Add(c); + int? width = videoStream == null ? null : videoStream.Width; + int? height = videoStream == null ? null : videoStream.Height; + int? bitDepth = videoStream == null ? null : videoStream.BitDepth; + int? videoBitrate = videoStream == null ? null : videoStream.BitRate; + double? videoLevel = videoStream == null ? null : videoStream.Level; + string videoProfile = videoStream == null ? null : videoStream.Profile; + float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate; + bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic; + string videoCodecTag = videoStream == null ? null : videoStream.CodecTag; + + TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; + int? packetLength = videoStream == null ? null : videoStream.PacketLength; + int? refFrames = videoStream == null ? null : videoStream.RefFrames; + + int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); + int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); + + if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag)) + { + LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item); + applyConditions = false; + break; + } + } + + if (applyConditions) + { + foreach (ProfileCondition c in i.Conditions) + { + audioTranscodingConditions.Add(c); + } + break; } - break; } } ApplyTranscodingConditions(playlistItem, audioTranscodingConditions); @@ -666,9 +745,23 @@ namespace MediaBrowser.Model.Dlna { if (i.Type == CodecType.Video && i.ContainsCodec(videoCodec, container)) { - foreach (ProfileCondition c in i.Conditions) + bool applyConditions = true; + foreach (ProfileCondition applyCondition in i.ApplyConditions) { - conditions.Add(c); + if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag)) + { + LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource); + applyConditions = false; + break; + } + } + + if (applyConditions) + { + foreach (ProfileCondition c in i.Conditions) + { + conditions.Add(c); + } } } } @@ -697,20 +790,35 @@ namespace MediaBrowser.Model.Dlna } conditions = new List(); + bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream); + foreach (CodecProfile i in profile.CodecProfiles) { if (i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec, container)) { - foreach (ProfileCondition c in i.Conditions) + bool applyConditions = true; + foreach (ProfileCondition applyCondition in i.ApplyConditions) { - conditions.Add(c); + if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioProfile, isSecondaryAudio)) + { + LogConditionFailure(profile, "VideoAudioCodecProfile", applyCondition, mediaSource); + applyConditions = false; + break; + } + } + + if (applyConditions) + { + foreach (ProfileCondition c in i.Conditions) + { + conditions.Add(c); + } } } } foreach (ProfileCondition i in conditions) { - bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream); if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio)) { LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource); diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs index 909760feef..baea0a06d2 100644 --- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs +++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs @@ -111,7 +111,8 @@ namespace MediaBrowser.Providers.TV Name = seasonName, IndexNumber = seasonNumber, Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)), - IsVirtualItem = isVirtualItem + IsVirtualItem = isVirtualItem, + SeriesId = series.Id }; season.SetParent(series); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 7458e7541d..a6c5d54292 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -33,6 +33,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.Channels; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Extensions; @@ -353,10 +354,6 @@ namespace MediaBrowser.Server.Implementations.Library private void RegisterItem(Guid id, BaseItem item) { - if (item.SourceType != SourceType.Library) - { - return; - } if (item is IItemByName) { if (!(item is MusicArtist)) @@ -364,14 +361,26 @@ namespace MediaBrowser.Server.Implementations.Library return; } } - if (item is Photo) - { - return; - } - if (!(item is Folder)) + + if (item.IsFolder) + { + if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel)) + { + if (item.SourceType != SourceType.Library) + { + return; + } + } + } + else { return; + if (item is Photo) + { + return; + } } + LibraryItemsCache.AddOrUpdate(id, item, delegate { return item; }); } @@ -782,19 +791,19 @@ namespace MediaBrowser.Server.Implementations.Library public BaseItem FindByPath(string path, bool? isFolder) { - var query = new InternalItemsQuery - { - Path = path, - IsFolder = isFolder - }; - // If this returns multiple items it could be tricky figuring out which one is correct. // In most cases, the newest one will be and the others obsolete but not yet cleaned up - return GetItemIds(query) - .Select(GetItemById) - .Where(i => i != null) - .OrderByDescending(i => i.DateCreated) + var query = new InternalItemsQuery + { + Path = path, + IsFolder = isFolder, + SortBy = new[] { ItemSortBy.DateCreated }, + SortOrder = SortOrder.Descending, + Limit = 1 + }; + + return GetItemList(query) .FirstOrDefault(); } @@ -1258,6 +1267,8 @@ namespace MediaBrowser.Server.Implementations.Library item = RetrieveItem(id); + //_logger.Debug("GetitemById {0}", id); + if (item != null) { RegisterItem(item); @@ -1508,7 +1519,7 @@ namespace MediaBrowser.Server.Implementations.Library UserId = user.Id.ToString("N") }, CancellationToken.None).Result; - + return channelResult.Items; } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 6d0f4ffe29..7b8832c594 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -30,22 +30,24 @@ 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().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 (string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + if (season != null || + string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || + args.HasParent()) { var episode = ResolveVideo(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().FirstOrDefault(); - } - var series = parent as Series; if (series == null) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 7d13b11ad1..eeac1345e8 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -38,10 +38,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV if (args.Parent is Series && args.IsDirectory) { var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); - + var series = ((Series)args.Parent); + var season = new Season { - IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber + IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber, + SeriesId = series.Id }; if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0) From 6b89a21c25fb7ff26817d3db8b819ee4882d5d01 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 9 Jul 2016 13:42:44 -0400 Subject: [PATCH 28/75] 3.1.60 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 0822195880..cc6b740b1a 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.59")] +[assembly: AssemblyVersion("3.1.60")] From 94582d1ed932d58880191d60fd8ccc4fa28c666b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 10 Jul 2016 11:43:33 -0400 Subject: [PATCH 29/75] fix episodes with dlna --- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 0a15491b67..84c1b3bdee 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -820,7 +820,7 @@ namespace MediaBrowser.Dlna.Didl var episode = item as Episode; if (episode != null) { - var parent = (BaseItem)episode.Series ?? episode.Season; + var parent = episode.Series; if (parent != null) { imageInfo = GetImageInfo(parent); From efebac4d6b0247d0be5faa22cb8857f04af39ade Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 10 Jul 2016 11:44:11 -0400 Subject: [PATCH 30/75] don't use hardware encoding with folder rips --- .../Playback/BaseStreamingService.cs | 36 +++++++++++++------ .../Encoder/BaseEncoder.cs | 8 +++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 27b675294b..15223a651d 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -286,19 +286,25 @@ namespace MediaBrowser.Api.Playback protected string GetH264Encoder(StreamState state) { - if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + // Only use alternative encoders for video files. + // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully + // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. + if (state.VideoType == VideoType.VideoFile) { - return "h264_qsv"; - } + if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) || + string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + { + return "h264_qsv"; + } - if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) - { - return "h264_nvenc"; - } - if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase)) - { - return "h264_omx"; + if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + return "h264_nvenc"; + } + if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase)) + { + return "h264_omx"; + } } return "libx264"; @@ -843,6 +849,14 @@ namespace MediaBrowser.Api.Playback return null; } + // Only use alternative encoders for video files. + // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully + // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. + if (state.VideoType != VideoType.VideoFile) + { + return null; + } + if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) { if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index 725f0bc6d0..32cd950afe 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -366,6 +366,14 @@ namespace MediaBrowser.MediaEncoding.Encoder return null; } + // Only use alternative encoders for video files. + // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully + // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. + if (state.VideoType != VideoType.VideoFile) + { + return null; + } + if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) { if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) From de635fe22c935acc238201b312eed1db2ccb46cd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 10 Jul 2016 11:44:53 -0400 Subject: [PATCH 31/75] add faster access to series sort name --- MediaBrowser.Api/StartupWizardService.cs | 2 +- .../Updates/GithubUpdater.cs | 6 ++- MediaBrowser.Controller/Entities/Book.cs | 6 +++ MediaBrowser.Controller/Entities/Folder.cs | 23 -------- .../Entities/IHasSeries.cs | 2 + .../Entities/TV/Episode.cs | 11 +++- MediaBrowser.Controller/Entities/TV/Season.cs | 9 ++++ MediaBrowser.Controller/Entities/TV/Series.cs | 9 +--- .../Library/LibraryManager.cs | 7 ++- .../Persistence/SqliteItemRepository.cs | 52 +++++++++++++++++-- .../Sorting/SeriesSortNameComparer.cs | 24 +-------- .../Api/DashboardService.cs | 11 ++-- 12 files changed, 94 insertions(+), 68 deletions(-) diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 87562b1266..1bebd42eb1 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -117,7 +117,7 @@ namespace MediaBrowser.Api config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; //config.EnableFolderView = true; - config.SchemaVersion = 107; + config.SchemaVersion = 108; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs index 2ffaedc4be..d1ec30210e 100644 --- a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs +++ b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs @@ -54,7 +54,9 @@ namespace MediaBrowser.Common.Implementations.Updates { if (updateLevel == PackageVersionClass.Release) { - obj = obj.Where(i => !i.prerelease).ToArray(); + // Technically all we need to do is check that it's not pre-release + // But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly. + obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray(); } else if (updateLevel == PackageVersionClass.Beta) { @@ -70,7 +72,7 @@ namespace MediaBrowser.Common.Implementations.Updates .Where(i => i != null) .OrderByDescending(i => Version.Parse(i.AvailableVersion)) .FirstOrDefault(); - + return availableUpdate ?? new CheckForUpdateResult { IsUpdateAvailable = false diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 9d35c86d25..2ca56bc706 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -22,7 +22,13 @@ namespace MediaBrowser.Controller.Entities public string SeriesName { get; set; } [IgnoreDataMember] public Guid? SeriesId { get; set; } + [IgnoreDataMember] + public string SeriesSortName { get; set; } + public string FindSeriesSortName() + { + return SeriesSortName; + } public string FindSeriesName() { return SeriesName; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 210c115641..89b9479e7b 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -760,11 +760,6 @@ namespace MediaBrowser.Controller.Entities Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue"); return true; } - if (query.SortBy.Contains(ItemSortBy.SeriesSortName, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName"); - return true; - } if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase)) { Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate"); @@ -859,24 +854,6 @@ namespace MediaBrowser.Controller.Entities return true; } - if (query.IsMissing.HasValue) - { - Logger.Debug("Query requires post-filtering due to IsMissing"); - return true; - } - - if (query.IsUnaired.HasValue) - { - Logger.Debug("Query requires post-filtering due to IsUnaired"); - return true; - } - - if (query.IsVirtualUnaired.HasValue) - { - Logger.Debug("Query requires post-filtering due to IsVirtualUnaired"); - return true; - } - if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager)) { Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems"); diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index d4dbb8ef67..531f587881 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -11,6 +11,8 @@ namespace MediaBrowser.Controller.Entities /// The name of the series. string SeriesName { get; set; } string FindSeriesName(); + string SeriesSortName { get; set; } + string FindSeriesSortName(); Guid? SeriesId { get; set; } Guid? FindSeriesId(); } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 78c35ad484..726390f65e 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -53,7 +53,16 @@ namespace MediaBrowser.Controller.Entities.TV /// This is the ending episode number for double episodes. /// /// The index number. - public int? IndexNumberEnd { get; set; } + public int? IndexNumberEnd { get; set; } + + [IgnoreDataMember] + public string SeriesSortName { get; set; } + + public string FindSeriesSortName() + { + var series = Series; + return series == null ? SeriesSortName : series.SortName; + } [IgnoreDataMember] protected override bool SupportsOwnedItems diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 865cadeb90..ee01c60b1b 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -51,6 +51,15 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public string SeriesSortName { get; set; } + + public string FindSeriesSortName() + { + var series = Series; + return series == null ? SeriesSortName : series.SortName; + } + // Genre, Rating and Stuido will all be the same protected override IEnumerable GetIndexByOptions() { diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 09ddbc6b60..ad35b3d369 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -449,13 +449,8 @@ namespace MediaBrowser.Controller.Entities.TV return true; } - if (!episode.ParentIndexNumber.HasValue) - { - var season = episode.Season; - return season != null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComparison.OrdinalIgnoreCase); - } - - return false; + var season = episode.Season; + return season != null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComparison.OrdinalIgnoreCase); }); } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index a6c5d54292..82be653e19 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1932,7 +1932,7 @@ namespace MediaBrowser.Server.Implementations.Library private string GetContentTypeOverride(string path, bool inherit) { - var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && _fileSystem.ContainsSubPath(i.Name, path))); + var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && !string.IsNullOrWhiteSpace(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); if (nameValuePair != null) { return nameValuePair.Value; @@ -2813,6 +2813,11 @@ namespace MediaBrowser.Server.Implementations.Library private void RemoveContentTypeOverrides(string path) { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException("path"); + } + var removeList = new List(); foreach (var contentType in ConfigurationManager.Configuration.ContentTypes) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index a3217e3ed3..5e09c1d0bd 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/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 = 107; + public const int LatestSchemaVersion = 108; /// /// Initializes a new instance of the class. @@ -272,6 +272,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.AddColumn(Logger, "TypedBaseItems", "SeasonName", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID"); _connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID"); + _connection.AddColumn(Logger, "TypedBaseItems", "SeriesSortName", "Text"); _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); @@ -412,7 +413,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "SeriesName", "SeasonName", "SeasonId", - "SeriesId" + "SeriesId", + "SeriesSortName" }; private readonly string[] _mediaStreamSaveColumns = @@ -535,7 +537,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "UserDataKey", "SeasonName", "SeasonId", - "SeriesId" + "SeriesId", + "SeriesSortName" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -982,10 +985,12 @@ namespace MediaBrowser.Server.Implementations.Persistence if (hasSeries != null) { _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesId(); + _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesSortName(); } else { _saveItemCommand.GetParameter(index++).Value = null; + _saveItemCommand.GetParameter(index++).Value = null; } _saveItemCommand.Transaction = transaction; @@ -1440,6 +1445,14 @@ namespace MediaBrowser.Server.Implementations.Persistence } } + if (hasSeries != null) + { + if (!reader.IsDBNull(63)) + { + hasSeries.SeriesSortName = reader.GetString(63); + } + } + return item; } @@ -3056,6 +3069,39 @@ namespace MediaBrowser.Server.Implementations.Persistence whereClauses.Add("LocationType<>'Virtual'"); } } + if (query.IsUnaired.HasValue) + { + if (query.IsUnaired.Value) + { + whereClauses.Add("PremiereDate >= DATETIME('now')"); + } + else + { + whereClauses.Add("PremiereDate < DATETIME('now')"); + } + } + if (query.IsMissing.HasValue && _config.Configuration.SchemaVersion >= 90) + { + if (query.IsMissing.Value) + { + whereClauses.Add("(IsVirtualItem=1 AND PremiereDate < DATETIME('now'))"); + } + else + { + whereClauses.Add("(IsVirtualItem=0 OR PremiereDate >= DATETIME('now'))"); + } + } + if (query.IsVirtualUnaired.HasValue && _config.Configuration.SchemaVersion >= 90) + { + if (query.IsVirtualUnaired.Value) + { + whereClauses.Add("(IsVirtualItem=1 AND PremiereDate >= DATETIME('now'))"); + } + else + { + whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))"); + } + } if (query.MediaTypes.Length == 1) { whereClauses.Add("MediaType=@MediaTypes"); diff --git a/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 4efc3218b2..6bc1264a48 100644 --- a/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Querying; using System; @@ -21,28 +20,9 @@ namespace MediaBrowser.Server.Implementations.Sorting private string GetValue(BaseItem item) { - Series series = null; + var hasSeries = item as IHasSeries; - var season = item as Season; - - if (season != null) - { - series = season.Series; - } - - var episode = item as Episode; - - if (episode != null) - { - series = episode.Series; - } - - if (series == null) - { - series = item as Series; - } - - return series != null ? series.SortName : null; + return hasSeries != null ? hasSeries.SeriesSortName : null; } /// diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 1a227126e3..d42382442d 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -349,8 +349,8 @@ namespace MediaBrowser.WebDashboard.Api } _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true); - _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true); - _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true); + //_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true); + //_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true); DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components")); @@ -358,12 +358,7 @@ namespace MediaBrowser.WebDashboard.Api DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src"); DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor"); DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st"); - DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src"); - - _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true); - _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true); - _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism"), true); - _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism-element"), true); + //DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src"); if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { From 3269223bc451ed76c72b0a90eb6ef002a55c338d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 10 Jul 2016 11:50:27 -0400 Subject: [PATCH 32/75] 3.1.61 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index cc6b740b1a..0f360578e2 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.60")] +[assembly: AssemblyVersion("3.1.61")] From 96efd27e2030703e7ab8b767c27c16f868c5ac22 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 11 Jul 2016 00:57:22 -0400 Subject: [PATCH 33/75] allow photos in home video libraries --- .../Library/Resolvers/PhotoResolver.cs | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 8beb03b71a..9dd30eddee 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -5,15 +5,19 @@ using MediaBrowser.Model.Entities; using System; using System.IO; using System.Linq; +using CommonIO; namespace MediaBrowser.Server.Implementations.Library.Resolvers { public class PhotoResolver : ItemResolver { private readonly IImageProcessor _imageProcessor; - public PhotoResolver(IImageProcessor imageProcessor) + private readonly ILibraryManager _libraryManager; + + public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager) { _imageProcessor = imageProcessor; + _libraryManager = libraryManager; } /// @@ -23,20 +27,45 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers /// Trailer. protected override Photo Resolve(ItemResolveArgs args) { - // Must be an image file within a photo collection - if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) && - !args.IsDirectory && - IsImageFile(args.Path, _imageProcessor)) + if (!args.IsDirectory) { - return new Photo + // Must be an image file within a photo collection + var collectionType = args.GetCollectionType(); + + if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) || + string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - Path = args.Path - }; + if (IsImageFile(args.Path, _imageProcessor)) + { + var filename = Path.GetFileNameWithoutExtension(args.Path); + + // Make sure the image doesn't belong to a video file + if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(i, filename))) + { + return null; + } + + return new Photo + { + Path = args.Path + }; + } + } } return null; } + private bool IsOwnedByMedia(FileSystemMetadata file, string imageFilename) + { + if (_libraryManager.IsVideoFile(file.FullName) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + private static readonly string[] IgnoreFiles = { "folder", @@ -44,7 +73,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers "landscape", "fanart", "backdrop", - "poster" + "poster", + "cover" }; internal static bool IsImageFile(string path, IImageProcessor imageProcessor) From 58d904a51a96bc15288b2ff0f99c87cdcb44728a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 11 Jul 2016 00:59:19 -0400 Subject: [PATCH 34/75] update next up --- MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs index 84d85d6675..c2a4339f0b 100644 --- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs +++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs @@ -154,7 +154,6 @@ namespace MediaBrowser.Server.Implementations.TV SortOrder = SortOrder.Descending, IsPlayed = true, Limit = 1, - IsVirtualItem = false, ParentIndexNumberNotEquals = 0 }).FirstOrDefault(); From beb61e71853694009e20832668608579ad786738 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 11 Jul 2016 12:57:46 -0400 Subject: [PATCH 35/75] 3.1.62 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 0f360578e2..f7461a7952 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.61")] +[assembly: AssemblyVersion("3.1.62")] From c29e2099cdde462d433068732070b5ab79b27728 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 12 Jul 2016 01:44:07 -0400 Subject: [PATCH 36/75] fix error when transcode fails to start --- MediaBrowser.Api/ApiEntryPoint.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index a677bc6004..dc811812ae 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -237,9 +237,12 @@ namespace MediaBrowser.Api { lock (_activeTranscodingJobs) { - var job = _activeTranscodingJobs.First(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); + var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - _activeTranscodingJobs.Remove(job); + if (job != null) + { + _activeTranscodingJobs.Remove(job); + } } if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) From c90a30a0fedd4b5424981795f4091d3c5d97ca67 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 12 Jul 2016 13:16:34 -0400 Subject: [PATCH 37/75] add safeguard on interval trigger duration --- MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs b/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs index 5107db6c4d..8038d5551d 100644 --- a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs @@ -60,7 +60,15 @@ namespace MediaBrowser.Common.ScheduledTasks triggerDate = DateTime.UtcNow.AddMinutes(1); } - Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.UtcNow, TimeSpan.FromMilliseconds(-1)); + var dueTime = triggerDate - DateTime.UtcNow; + var maxDueTime = TimeSpan.FromDays(7); + + if (dueTime > maxDueTime) + { + dueTime = maxDueTime; + } + + Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } /// From 4f6a3138a65e4dbe356acec36f0f51de74486737 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 12 Jul 2016 13:23:08 -0400 Subject: [PATCH 38/75] 3.1.63 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index f7461a7952..49b9fedf99 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.62")] +[assembly: AssemblyVersion("3.1.63")] From 5bd44644cc0d9f4751fb371b95f18c4d1e6f4965 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 13 Jul 2016 13:46:04 -0400 Subject: [PATCH 39/75] update hls subtitle display name --- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 780e20f875..8ea279f1a3 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -572,13 +572,11 @@ namespace MediaBrowser.Api.Playback.Hls { const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\""; - var name = stream.Language; + var name = stream.DisplayTitle; var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index; var isForced = stream.IsForced; - if (string.IsNullOrWhiteSpace(name)) name = stream.Codec ?? "Unknown"; - var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}", state.Request.MediaSourceId, stream.Index.ToString(UsCulture), From 2254546ea53e51a263e9760e2402bcfec851e62c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 13 Jul 2016 15:09:31 -0400 Subject: [PATCH 40/75] add all subtitles to hls manifest --- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 2 +- MediaBrowser.Api/Playback/StreamRequest.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 8ea279f1a3..36ab20f0fe 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -525,7 +525,7 @@ namespace MediaBrowser.Api.Playback.Hls var subtitleGroup = subtitleStreams.Count > 0 && request is GetMasterHlsVideoPlaylist && - ((GetMasterHlsVideoPlaylist)request).SubtitleMethod == SubtitleDeliveryMethod.Hls ? + (((GetMasterHlsVideoPlaylist)request).SubtitleMethod == SubtitleDeliveryMethod.Hls || ((GetMasterHlsVideoPlaylist)request).EnableSubtitlesInManifest) ? "subs" : null; diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 370915ec38..a8ca6aaa3b 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -194,6 +194,8 @@ namespace MediaBrowser.Api.Playback public bool ForceLiveStream { get; set; } + public bool EnableSubtitlesInManifest { get; set; } + public VideoStreamRequest() { EnableAutoStreamCopy = true; From e44a24d9e5529dd4863713bb7059255e9be6cb6e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 13 Jul 2016 15:16:51 -0400 Subject: [PATCH 41/75] update hls subtitles --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 8 ++++++++ MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 8 +++++++- MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 + MediaBrowser.Model/Dlna/StreamInfo.cs | 2 ++ MediaBrowser.Model/Dlna/TranscodingProfile.cs | 3 +++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 15223a651d..f60a106dac 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1568,6 +1568,13 @@ namespace MediaBrowser.Api.Playback { request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture); } + else if (i == 28) + { + if (videoRequest != null) + { + videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + } } } @@ -2140,6 +2147,7 @@ namespace MediaBrowser.Api.Playback { state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps; state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream; + state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; } } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 36ab20f0fe..f4ecf36934 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -525,10 +525,16 @@ namespace MediaBrowser.Api.Playback.Hls var subtitleGroup = subtitleStreams.Count > 0 && request is GetMasterHlsVideoPlaylist && - (((GetMasterHlsVideoPlaylist)request).SubtitleMethod == SubtitleDeliveryMethod.Hls || ((GetMasterHlsVideoPlaylist)request).EnableSubtitlesInManifest) ? + (state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest.EnableSubtitlesInManifest) ? "subs" : null; + // If we're burning in subtitles then don't add additional subs to the manifest + if (state.SubtitleStream != null && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode) + { + subtitleGroup = null; + } + if (!string.IsNullOrWhiteSpace(subtitleGroup)) { AddSubtitles(state, subtitleStreams, builder); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index e07e56d397..2863eba2e0 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -480,6 +480,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.VideoCodec = transcodingProfile.VideoCodec; playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps; playlistItem.ForceLiveStream = transcodingProfile.ForceLiveStream; + playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels)) { diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 43a31f6492..f95c6a0707 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -37,6 +37,7 @@ namespace MediaBrowser.Model.Dlna public bool CopyTimestamps { get; set; } public bool ForceLiveStream { get; set; } + public bool EnableSubtitlesInManifest { get; set; } public string[] AudioCodecs { get; set; } public int? AudioStreamIndex { get; set; } @@ -249,6 +250,7 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty)); list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.TranscodingMaxAudioChannels.Value) : string.Empty)); + list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString().ToLower())); return list; } diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index d1314c17bf..19caf85eb2 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -38,6 +38,9 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("forceLiveStream")] public bool ForceLiveStream { get; set; } + [XmlAttribute("enableSubtitlesInManifest")] + public bool EnableSubtitlesInManifest { get; set; } + [XmlAttribute("maxAudioChannels")] public string MaxAudioChannels { get; set; } From 1f443097b7fa6f9ff829157120c4589d3ed65754 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 13 Jul 2016 22:36:42 -0400 Subject: [PATCH 42/75] improve prevention of simultaneous instances --- .../ApplicationHost.cs | 2 +- MediaBrowser.ServerApplication/MainStartup.cs | 58 +++++++++++++++++-- .../MediaBrowser.ServerApplication.csproj | 1 + 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index bcd2ed8bd8..b7ea5bdad8 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -274,7 +274,7 @@ namespace MediaBrowser.Server.Startup.Common { get { - return "Media Browser Server"; + return "Emby Server"; } } diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index fb4fb86ffd..0f2fea1556 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -12,6 +12,7 @@ using System.Configuration.Install; using System.Diagnostics; using System.IO; using System.Linq; +using System.Management; using System.Runtime.InteropServices; using System.ServiceProcess; using System.Threading; @@ -102,7 +103,7 @@ namespace MediaBrowser.ServerApplication if (IsAlreadyRunning(applicationPath, currentProcess)) { - logger.Info("Shutting down because another instance of Media Browser Server is already running."); + logger.Info("Shutting down because another instance of Emby Server is already running."); return; } @@ -130,13 +131,28 @@ namespace MediaBrowser.ServerApplication /// true if [is already running] [the specified current process]; otherwise, false. private static bool IsAlreadyRunning(string applicationPath, Process currentProcess) { - var filename = Path.GetFileName(applicationPath); - var duplicate = Process.GetProcesses().FirstOrDefault(i => { try { - return string.Equals(filename, Path.GetFileName(i.MainModule.FileName)) && currentProcess.Id != i.Id; + if (currentProcess.Id == i.Id) + { + return false; + } + } + catch (Exception) + { + return false; + } + + try + { + //_logger.Info("Module: {0}", i.MainModule.FileName); + if (string.Equals(applicationPath, i.MainModule.FileName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + return false; } catch (Exception) { @@ -155,6 +171,40 @@ namespace MediaBrowser.ServerApplication } } + if (!_isRunningAsService) + { + return IsAlreadyRunningAsService(applicationPath); + } + + return false; + } + + private static bool IsAlreadyRunningAsService(string applicationPath) + { + var serviceName = BackgroundService.GetExistingServiceName(); + + WqlObjectQuery wqlObjectQuery = new WqlObjectQuery(string.Format("SELECT * FROM Win32_Service WHERE State = 'Running' AND Name = '{0}'", serviceName)); + ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(wqlObjectQuery); + ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get(); + + foreach (ManagementObject managementObject in managementObjectCollection) + { + var obj = managementObject.GetPropertyValue("PathName"); + if (obj == null) + { + continue; + } + var path = obj.ToString(); + + _logger.Info("Service path: {0}", path); + // Need to use indexOf instead of equality because the path will have the full service command line + if (path.IndexOf(applicationPath, StringComparison.OrdinalIgnoreCase) != -1) + { + _logger.Info("The windows service is already running"); + return true; + } + } + return false; } diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index fcd6f7fafe..b01d8c43f6 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -88,6 +88,7 @@ True + From 7e52b0d3041bd0881cd9b8c6e2e1103a700b57be Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 13 Jul 2016 23:36:23 -0400 Subject: [PATCH 43/75] add service message --- MediaBrowser.Server.Implementations/Dto/DtoService.cs | 2 +- MediaBrowser.ServerApplication/MainStartup.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index de6c23cac6..975e292f0d 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1322,7 +1322,7 @@ namespace MediaBrowser.Server.Implementations.Dto } } - if (fields.Contains(ItemFields.SeriesPrimaryImage)) + //if (fields.Contains(ItemFields.SeriesPrimaryImage)) { episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 0f2fea1556..2440eab7ce 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -201,6 +201,7 @@ namespace MediaBrowser.ServerApplication if (path.IndexOf(applicationPath, StringComparison.OrdinalIgnoreCase) != -1) { _logger.Info("The windows service is already running"); + MessageBox.Show("Emby Server is already running as a Windows Service. Only one instance is allowed at a time. To run as a tray icon, shut down the Windows Service."); return true; } } From 61dab02b883b63e7e313c62961e623514b23e7a4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 13 Jul 2016 23:37:21 -0400 Subject: [PATCH 44/75] 3.1.64 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 49b9fedf99..4c5bc4b04a 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.63")] +[assembly: AssemblyVersion("3.1.64")] From 1900afb311aeb394ee6663e834cb2a25cd44c375 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 14 Jul 2016 15:13:52 -0400 Subject: [PATCH 45/75] update components --- .../HttpServer/HttpListenerHost.cs | 41 +++++++-------- .../HttpServer/StreamWriter.cs | 51 +++++++++++++------ ...MediaBrowser.Server.Implementations.csproj | 4 +- .../packages.config | 2 +- 4 files changed, 58 insertions(+), 40 deletions(-) diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index f091f0f1f7..17e4793cb5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -335,7 +335,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// The HTTP req. /// The URL. /// Task. - protected Task RequestHandler(IHttpRequest httpReq, Uri url) + protected async Task RequestHandler(IHttpRequest httpReq, Uri url) { var date = DateTime.Now; @@ -345,7 +345,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer { httpRes.StatusCode = 503; httpRes.Close(); - return Task.FromResult(true); + return ; } var operationName = httpReq.OperationName; @@ -365,13 +365,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl(DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl("emby/" + DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) || @@ -389,35 +389,35 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpRes.Write("EmbyPlease update your Emby bookmark to " + newUrl + ""); httpRes.Close(); - return Task.FromResult(true); + return; } } if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl(DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl("../" + DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl(DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.IsNullOrEmpty(localPath)) { httpRes.RedirectToUrl("/" + DefaultRedirectPath); - return Task.FromResult(true); + return; } if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase)) { httpRes.RedirectToUrl("web/pin.html"); - return Task.FromResult(true); + return; } if (!string.IsNullOrWhiteSpace(GlobalResponse)) @@ -427,7 +427,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpRes.Write(GlobalResponse); httpRes.Close(); - return Task.FromResult(true); + return; } var handler = HttpHandlerFactory.GetHandler(httpReq); @@ -443,13 +443,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName(); } - var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName); - - task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); - //Matches Exceptions handled in HttpListenerBase.InitTask() - - task.ContinueWith(x => + try { + await serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false); + } + finally + { + httpRes.Close(); var statusCode = httpRes.StatusCode; var duration = DateTime.Now - date; @@ -458,13 +458,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer { LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration); } - - }, TaskContinuationOptions.None); - return task; + } } - return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo) - .AsTaskException(); + throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo); } /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs index a756f4aa8a..e38e393220 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs @@ -4,13 +4,15 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Threading.Tasks; +using ServiceStack; namespace MediaBrowser.Server.Implementations.HttpServer { /// /// Class StreamWriter /// - public class StreamWriter : IStreamWriter, IHasOptions + public class StreamWriter : IStreamWriter, /*IAsyncStreamWriter,*/ IHasOptions { private ILogger Logger { get; set; } @@ -73,24 +75,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer { } + // 256k + private const int BufferSize = 262144; + /// /// Writes to. /// /// The response stream. public void WriteTo(Stream responseStream) - { - WriteToInternal(responseStream); - } - - // 256k - private const int BufferSize = 262144; - - /// - /// Writes to async. - /// - /// The response stream. - /// Task. - private void WriteToInternal(Stream responseStream) { try { @@ -107,7 +99,36 @@ namespace MediaBrowser.Server.Implementations.HttpServer { OnError(); } - + + throw; + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } + } + + public async Task WriteToAsync(Stream responseStream) + { + try + { + using (var src = SourceStream) + { + await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Logger.ErrorException("Error streaming data", ex); + + if (OnError != null) + { + OnError(); + } + throw; } finally diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index c783069118..2102eef82b 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -73,8 +73,8 @@ ..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll True - - ..\packages\SocketHttpListener.1.0.0.30\lib\net45\SocketHttpListener.dll + + ..\packages\SocketHttpListener.1.0.0.32\lib\net45\SocketHttpListener.dll True diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 9592ecb169..c4b46481e8 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -9,5 +9,5 @@ - + \ No newline at end of file From 61aa25c3585d5f7acf1b88ddaf30a8b04e0c466e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 14 Jul 2016 23:57:38 -0400 Subject: [PATCH 46/75] update SocketHttpListener --- .../MediaBrowser.Server.Implementations.csproj | 4 ++-- MediaBrowser.Server.Implementations/packages.config | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 2102eef82b..8a3f6616a3 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -73,8 +73,8 @@ ..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll True - - ..\packages\SocketHttpListener.1.0.0.32\lib\net45\SocketHttpListener.dll + + ..\packages\SocketHttpListener.1.0.0.33\lib\net45\SocketHttpListener.dll True diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index c4b46481e8..5233ac5903 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -9,5 +9,5 @@ - + \ No newline at end of file From 3f8588f94bb33e0342a75b7f8cc1d140790b446e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 14 Jul 2016 23:58:51 -0400 Subject: [PATCH 47/75] 3.1.65 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 4c5bc4b04a..edb79a7c51 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.64")] +[assembly: AssemblyVersion("3.1.65")] From 2e91d69d20e49f971d9890674d3016351ee87ccd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 15 Jul 2016 13:13:55 -0400 Subject: [PATCH 48/75] update async stream writing --- .../BaseProgressiveStreamingService.cs | 11 +- .../Progressive/ProgressiveStreamWriter.cs | 12 +- .../Net/IHttpResultFactory.cs | 2 + .../HttpServer/AsyncStreamWriterFunc.cs | 56 ++++ .../HttpServer/HttpResultFactory.cs | 5 + .../NetListener/HttpListenerServer.cs | 285 ------------------ .../HttpServer/RangeRequestWriter.cs | 74 ++++- .../HttpServer/StreamWriter.cs | 2 +- ...MediaBrowser.Server.Implementations.csproj | 5 +- 9 files changed, 141 insertions(+), 311 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 868d8d4889..d8b7ce2ef3 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -13,6 +13,7 @@ using ServiceStack.Web; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Threading; using System.Threading.Tasks; using CommonIO; @@ -336,17 +337,19 @@ namespace MediaBrowser.Api.Playback.Progressive state.Dispose(); } - var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - result.Options["Content-Type"] = contentType; + outputHeaders["Content-Type"] = contentType; // Add the response headers to the result object foreach (var item in responseHeaders) { - result.Options[item.Key] = item.Value; + outputHeaders[item.Key] = item.Value; } - return result; + Func streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream); + + return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders); } finally { diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 9f02c51cd6..4c9428cc44 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -48,21 +48,19 @@ namespace MediaBrowser.Api.Playback.Progressive /// The response stream. public void WriteTo(Stream responseStream) { - WriteToInternal(responseStream); + var task = WriteToAsync(responseStream); + Task.WaitAll(task); } /// - /// Writes to async. + /// Writes to. /// /// The response stream. - /// Task. - private void WriteToInternal(Stream responseStream) + public async Task WriteToAsync(Stream responseStream) { try { - var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream); - - Task.WaitAll(task); + await new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream).ConfigureAwait(false); } catch (IOException) { diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs index cd7ee603e3..49d4614d81 100644 --- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs @@ -28,6 +28,8 @@ namespace MediaBrowser.Controller.Net /// System.Object. object GetResult(object content, string contentType, IDictionary responseHeaders = null); + object GetAsyncStreamWriter(Func streamWriter, IDictionary responseHeaders = null); + /// /// Gets the optimized result. /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs new file mode 100644 index 0000000000..4f8b18319d --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using ServiceStack; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer +{ + public class AsyncStreamWriterFunc : IStreamWriter, IAsyncStreamWriter, IHasOptions + { + /// + /// Gets or sets the source stream. + /// + /// The source stream. + private Func Writer { get; set; } + + /// + /// Gets the options. + /// + /// The options. + public IDictionary Options { get; } + + public Action OnComplete { get; set; } + public Action OnError { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public AsyncStreamWriterFunc(Func writer, IDictionary headers) + { + Writer = writer; + + if (headers == null) + { + headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + Options = headers; + } + + /// + /// Writes to. + /// + /// The response stream. + public void WriteTo(Stream responseStream) + { + var task = Writer(responseStream); + Task.WaitAll(task); + } + + public async Task WriteToAsync(Stream responseStream) + { + await Writer(responseStream).ConfigureAwait(false); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 1d48292601..c520e43b8b 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -699,5 +699,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw error; } + + public object GetAsyncStreamWriter(Func streamWriter, IDictionary responseHeaders = null) + { + return new AsyncStreamWriterFunc(streamWriter, responseHeaders); + } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs deleted file mode 100644 index 31c0e87b33..0000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs +++ /dev/null @@ -1,285 +0,0 @@ -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Logging; -using ServiceStack; -using ServiceStack.Host.HttpListener; -using ServiceStack.Web; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.HttpServer.NetListener -{ - public class HttpListenerServer : IHttpListener - { - private readonly ILogger _logger; - private HttpListener _listener; - private readonly ManualResetEventSlim _listenForNextRequest = new ManualResetEventSlim(false); - - public Action ErrorHandler { get; set; } - public Action WebSocketHandler { get; set; } - public Func RequestHandler { get; set; } - - private readonly Action _endpointListener; - - public HttpListenerServer(ILogger logger, Action endpointListener) - { - _logger = logger; - _endpointListener = endpointListener; - } - - private List UrlPrefixes { get; set; } - - public void Start(IEnumerable urlPrefixes) - { - UrlPrefixes = urlPrefixes.ToList(); - - if (_listener == null) - _listener = new HttpListener(); - - //HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); - - foreach (var prefix in UrlPrefixes) - { - _logger.Info("Adding HttpListener prefix " + prefix); - _listener.Prefixes.Add(prefix); - } - - _listener.Start(); - - Task.Factory.StartNew(Listen, TaskCreationOptions.LongRunning); - } - - private bool IsListening - { - get { return _listener != null && _listener.IsListening; } - } - - // Loop here to begin processing of new requests. - private void Listen() - { - while (IsListening) - { - if (_listener == null) return; - _listenForNextRequest.Reset(); - - try - { - _listener.BeginGetContext(ListenerCallback, _listener); - _listenForNextRequest.Wait(); - } - catch (Exception ex) - { - _logger.Error("Listen()", ex); - return; - } - if (_listener == null) return; - } - } - - // Handle the processing of a request in here. - private void ListenerCallback(IAsyncResult asyncResult) - { - _listenForNextRequest.Set(); - - var listener = asyncResult.AsyncState as HttpListener; - HttpListenerContext context; - - if (listener == null) return; - var isListening = listener.IsListening; - - try - { - if (!isListening) - { - _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return; - } - // The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework, - // blocks until there is a request to be processed or some type of data is available. - context = listener.EndGetContext(asyncResult); - } - catch (Exception ex) - { - // You will get an exception when httpListener.Stop() is called - // because there will be a thread stopped waiting on the .EndGetContext() - // method, and again, that is just the way most Begin/End asynchronous - // methods of the .NET Framework work. - var errMsg = ex + ": " + IsListening; - _logger.Warn(errMsg); - return; - } - - Task.Factory.StartNew(() => InitTask(context)); - } - - private void InitTask(HttpListenerContext context) - { - try - { - var task = this.ProcessRequestAsync(context); - task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); - - if (task.Status == TaskStatus.Created) - { - task.RunSynchronously(); - } - } - catch (Exception ex) - { - HandleError(ex, context); - } - } - - private Task ProcessRequestAsync(HttpListenerContext context) - { - var request = context.Request; - - LogHttpRequest(request); - - if (request.IsWebSocketRequest) - { - return ProcessWebSocketRequest(context); - } - - if (string.IsNullOrEmpty(context.Request.RawUrl)) - return ((object)null).AsTaskResult(); - - var operationName = context.Request.GetOperationName(); - - var httpReq = GetRequest(context, operationName); - - return RequestHandler(httpReq, request.Url); - } - - /// - /// Processes the web socket request. - /// - /// The CTX. - /// Task. - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) - { -#if !__MonoCS__ - try - { - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); - - if (WebSocketHandler != null) - { - WebSocketHandler(new WebSocketConnectEventArgs - { - WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger), - Endpoint = ctx.Request.RemoteEndPoint.ToString() - }); - } - } - catch (Exception ex) - { - _logger.ErrorException("AcceptWebSocketAsync error", ex); - ctx.Response.StatusCode = 500; - ctx.Response.Close(); - } -#endif - } - - private void HandleError(Exception ex, HttpListenerContext context) - { - var operationName = context.Request.GetOperationName(); - var httpReq = GetRequest(context, operationName); - - if (ErrorHandler != null) - { - ErrorHandler(ex, httpReq); - } - } - - private static ListenerRequest GetRequest(HttpListenerContext httpContext, string operationName) - { - var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None); - req.RequestAttributes = req.GetAttributes(); - - return req; - } - - /// - /// Logs the HTTP request. - /// - /// The request. - private void LogHttpRequest(HttpListenerRequest request) - { - var endpoint = request.LocalEndPoint; - - if (endpoint != null) - { - var address = endpoint.ToString(); - - _endpointListener(address); - } - - LogRequest(_logger, request); - } - - /// - /// Logs the request. - /// - /// The logger. - /// The request. - private static void LogRequest(ILogger logger, HttpListenerRequest request) - { - var log = new StringBuilder(); - - var logHeaders = true; - - if (logHeaders) - { - var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k])); - - log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers); - } - - var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod; - - logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log); - } - - public void Stop() - { - if (_listener != null) - { - foreach (var prefix in UrlPrefixes) - { - _listener.Prefixes.Remove(prefix); - } - - _listener.Close(); - } - } - - public void Dispose() - { - Dispose(true); - } - - private bool _disposed; - private readonly object _disposeLock = new object(); - protected virtual void Dispose(bool disposing) - { - if (_disposed) return; - - lock (_disposeLock) - { - if (_disposed) return; - - if (disposing) - { - Stop(); - } - - //release unmanaged resources here... - _disposed = true; - } - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index fb4397462b..7ac92408b2 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; +using System.Threading.Tasks; +using ServiceStack; namespace MediaBrowser.Server.Implementations.HttpServer { - public class RangeRequestWriter : IStreamWriter, IHttpResult + public class RangeRequestWriter : IStreamWriter, IAsyncStreamWriter, IHttpResult { /// /// Gets or sets the source stream. @@ -168,16 +170,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// /// The response stream. public void WriteTo(Stream responseStream) - { - WriteToInternal(responseStream); - } - - /// - /// Writes to async. - /// - /// The response stream. - /// Task. - private void WriteToInternal(Stream responseStream) { try { @@ -237,6 +229,66 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } + public async Task WriteToAsync(Stream responseStream) + { + try + { + // Headers only + if (IsHeadRequest) + { + return; + } + + using (var source = SourceStream) + { + // If the requested range is "0-", we can optimize by just doing a stream copy + if (RangeEnd >= TotalContentLength - 1) + { + await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); + } + else + { + await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false); + } + } + } + catch (IOException ex) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error in range request writer", ex); + throw; + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } + } + + private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength) + { + var array = new byte[BufferSize]; + int count; + while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) + { + var bytesToCopy = Math.Min(count, copyLength); + + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); + + copyLength -= bytesToCopy; + + if (copyLength <= 0) + { + break; + } + } + } + public string ContentType { get; set; } public IRequest RequestContext { get; set; } diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs index e38e393220..f5906f6b7b 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// /// Class StreamWriter /// - public class StreamWriter : IStreamWriter, /*IAsyncStreamWriter,*/ IHasOptions + public class StreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions { private ILogger Logger { get; set; } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 8a3f6616a3..5877059d7a 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -156,6 +156,7 @@ + @@ -757,9 +758,7 @@ - - - +