diff options
| author | Luke <luke.pulverenti@gmail.com> | 2017-05-31 15:40:34 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-05-31 15:40:34 -0400 |
| commit | 91176d9ccc1dde8155c10411c70e62a9f4b059d5 (patch) | |
| tree | 21365f5a8dd09534a53d9f88d2a7a3116f3f3f98 /Emby.Server.Implementations | |
| parent | c37c9a75073b1b9caa3af2c3bc62abd837bd630e (diff) | |
| parent | 4e10daf646e0788409f2bc52ef70effa2616e3f3 (diff) | |
Merge pull request #2677 from MediaBrowser/beta
Beta
Diffstat (limited to 'Emby.Server.Implementations')
79 files changed, 1819 insertions, 1037 deletions
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 0cdd934b7..809771b04 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -23,7 +23,7 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.IO; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; @@ -120,7 +120,7 @@ namespace Emby.Server.Implementations.Channels if (query.IsFavorite.HasValue) { var val = query.IsFavorite.Value; - channels = channels.Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val) + channels = channels.Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val) .ToList(); } @@ -263,7 +263,7 @@ namespace Emby.Server.Implementations.Channels } catch { - + } return; } @@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.Channels _jsonSerializer.SerializeToFile(mediaSources, path); } - public async Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken) + public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken) { IEnumerable<ChannelMediaInfo> results = GetSavedMediaSources(item); @@ -460,12 +460,12 @@ namespace Emby.Server.Implementations.Channels public IEnumerable<ChannelFeatures> GetAllChannelFeatures() { - return _libraryManager.GetItemList(new InternalItemsQuery + return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Channel).Name }, SortBy = new[] { ItemSortBy.SortName } - }).Select(i => GetChannelFeatures(i.Id.ToString("N"))); + }).Select(i => GetChannelFeatures(i.ToString("N"))); } public ChannelFeatures GetChannelFeatures(string id) @@ -963,7 +963,7 @@ namespace Emby.Server.Implementations.Channels } } - return await GetReturnItems(internalItems, providerTotalRecordCount, user, query).ConfigureAwait(false); + return GetReturnItems(internalItems, providerTotalRecordCount, user, query); } public async Task<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken) @@ -1154,7 +1154,7 @@ namespace Emby.Server.Implementations.Channels filename + ".json"); } - private async Task<QueryResult<BaseItem>> GetReturnItems(IEnumerable<BaseItem> items, + private QueryResult<BaseItem> GetReturnItems(IEnumerable<BaseItem> items, int? totalCountFromProvider, User user, ChannelItemQuery query) diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index b82d4e44e..463d276e5 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Collections return base.Supports(item); } - protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item) + protected override List<BaseItem> GetItemsWithImages(IHasImages item) { var playlist = (BoxSet)item; @@ -73,10 +73,10 @@ namespace Emby.Server.Implementations.Collections .DistinctBy(i => i.Id) .ToList(); - return Task.FromResult(GetFinalItems(items, 2)); + return GetFinalItems(items, 2); } - protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); } diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 9c26655fc..4e5d344a3 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -170,6 +170,11 @@ namespace Emby.Server.Implementations.Collections { var item = _libraryManager.GetItemById(itemId); + if (string.IsNullOrWhiteSpace(item.Path)) + { + continue; + } + if (item == null) { throw new ArgumentException("No item exists with the supplied Id"); diff --git a/Emby.Server.Implementations/Collections/CollectionsDynamicFolder.cs b/Emby.Server.Implementations/Collections/CollectionsDynamicFolder.cs index 4ff33e645..c7bcdfe25 100644 --- a/Emby.Server.Implementations/Collections/CollectionsDynamicFolder.cs +++ b/Emby.Server.Implementations/Collections/CollectionsDynamicFolder.cs @@ -1,7 +1,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using System.IO; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.IO; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.IO; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 451ec2dfe..3c4b50eda 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -207,8 +207,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "DateCreated", "DATETIME", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateModified", "DATETIME", existingColumnNames); - AddColumn(db, "TypedBaseItems", "ForcedSortName", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsSeries", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsLive", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsNews", "BIT", existingColumnNames); @@ -241,7 +239,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "InheritedTags", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "CleanName", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "PresentationUniqueKey", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "SlugName", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "OriginalTitle", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "PrimaryVersionId", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames); @@ -427,7 +424,7 @@ namespace Emby.Server.Implementations.Data "OfficialRating", "HomePageUrl", "DisplayMediaType", - "ForcedSortName", + "SortName", "RunTimeTicks", "VoteCount", "DateCreated", @@ -550,7 +547,6 @@ namespace Emby.Server.Implementations.Data "DisplayMediaType", "DateCreated", "DateModified", - "ForcedSortName", "PreferredMetadataLanguage", "PreferredMetadataCountryCode", "IsHD", @@ -573,7 +569,6 @@ namespace Emby.Server.Implementations.Data "InheritedTags", "CleanName", "PresentationUniqueKey", - "SlugName", "OriginalTitle", "PrimaryVersionId", "DateLastMediaAdded", @@ -828,8 +823,6 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@DateCreated", item.DateCreated); saveItemStatement.TryBind("@DateModified", item.DateModified); - saveItemStatement.TryBind("@ForcedSortName", item.ForcedSortName); - saveItemStatement.TryBind("@PreferredMetadataLanguage", item.PreferredMetadataLanguage); saveItemStatement.TryBind("@PreferredMetadataCountryCode", item.PreferredMetadataCountryCode); saveItemStatement.TryBind("@IsHD", item.IsHD); @@ -950,7 +943,6 @@ namespace Emby.Server.Implementations.Data } saveItemStatement.TryBind("@PresentationUniqueKey", item.PresentationUniqueKey); - saveItemStatement.TryBind("@SlugName", item.SlugName); saveItemStatement.TryBind("@OriginalTitle", item.OriginalTitle); var video = item as Video; @@ -1240,7 +1232,7 @@ namespace Emby.Server.Implementations.Data foreach (var row in statement.ExecuteQuery()) { - return GetItem(row); + return GetItem(row, new InternalItemsQuery()); } } @@ -1249,19 +1241,10 @@ namespace Emby.Server.Implementations.Data } } - private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader) - { - return GetItem(reader, new InternalItemsQuery()); - } - private bool TypeRequiresDeserialization(Type type) { if (_config.Configuration.SkipDeserializationForBasicTypes) { - if (type == typeof(Person)) - { - return false; - } if (type == typeof(Channel)) { return false; @@ -1280,6 +1263,10 @@ namespace Emby.Server.Implementations.Data } } + if (type == typeof(Person)) + { + return false; + } if (type == typeof(MusicGenre)) { return false; @@ -1361,6 +1348,11 @@ namespace Emby.Server.Implementations.Data private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader, InternalItemsQuery query) { + return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query)); + } + + private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields) + { var typeString = reader.GetString(0); var type = _typeMapper.GetType(typeString); @@ -1407,87 +1399,96 @@ namespace Emby.Server.Implementations.Data return null; } - if (!reader.IsDBNull(2)) + var index = 2; + + if (queryHasStartDate) { - var hasStartDate = item as IHasStartDate; - if (hasStartDate != null) + if (!reader.IsDBNull(index)) { - hasStartDate.StartDate = reader[2].ReadDateTime(); + var hasStartDate = item as IHasStartDate; + if (hasStartDate != null) + { + hasStartDate.StartDate = reader[index].ReadDateTime(); + } } + index++; } - if (!reader.IsDBNull(3)) + if (!reader.IsDBNull(index)) { - item.EndDate = reader[3].ReadDateTime(); + item.EndDate = reader[index].ReadDateTime(); } + index++; - if (!reader.IsDBNull(4)) + if (!reader.IsDBNull(index)) { - item.ChannelId = reader.GetString(4); + item.ChannelId = reader.GetString(index); } + index++; - var index = 5; - - var hasProgramAttributes = item as IHasProgramAttributes; - if (hasProgramAttributes != null) + if (enableProgramAttributes) { - if (!reader.IsDBNull(index)) + var hasProgramAttributes = item as IHasProgramAttributes; + if (hasProgramAttributes != null) { - hasProgramAttributes.IsMovie = reader.GetBoolean(index); - } - index++; + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.IsMovie = reader.GetBoolean(index); + } + index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsSports = reader.GetBoolean(index); - } - index++; + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.IsSports = reader.GetBoolean(index); + } + index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsKids = reader.GetBoolean(index); - } - index++; + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.IsKids = reader.GetBoolean(index); + } + index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsSeries = reader.GetBoolean(index); - } - index++; + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.IsSeries = reader.GetBoolean(index); + } + index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsLive = reader.GetBoolean(index); - } - index++; + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.IsLive = reader.GetBoolean(index); + } + index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsNews = reader.GetBoolean(index); - } - index++; + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.IsNews = reader.GetBoolean(index); + } + index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsPremiere = reader.GetBoolean(index); - } - index++; + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.IsPremiere = reader.GetBoolean(index); + } + index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.EpisodeTitle = reader.GetString(index); - } - index++; + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.EpisodeTitle = reader.GetString(index); + } + index++; - if (!reader.IsDBNull(index)) + if (!reader.IsDBNull(index)) + { + hasProgramAttributes.IsRepeat = reader.GetBoolean(index); + } + index++; + } + else { - hasProgramAttributes.IsRepeat = reader.GetBoolean(index); + index += 9; } - index++; - } - else - { - index += 9; } if (!reader.IsDBNull(index)) @@ -1496,7 +1497,7 @@ namespace Emby.Server.Implementations.Data } index++; - if (query.HasField(ItemFields.CustomRating)) + if (HasField(query, ItemFields.CustomRating)) { if (!reader.IsDBNull(index)) { @@ -1511,7 +1512,7 @@ namespace Emby.Server.Implementations.Data } index++; - if (query.HasField(ItemFields.Settings)) + if (HasField(query, ItemFields.Settings)) { if (!reader.IsDBNull(index)) { @@ -1538,17 +1539,23 @@ namespace Emby.Server.Implementations.Data } index++; - if (!reader.IsDBNull(index)) + if (HasField(query, ItemFields.ExternalEtag)) { - item.ExternalEtag = reader.GetString(index); + if (!reader.IsDBNull(index)) + { + item.ExternalEtag = reader.GetString(index); + } + index++; } - index++; - if (!reader.IsDBNull(index)) + if (HasField(query, ItemFields.DateLastRefreshed)) { - item.DateLastRefreshed = reader[index].ReadDateTime(); + if (!reader.IsDBNull(index)) + { + item.DateLastRefreshed = reader[index].ReadDateTime(); + } + index++; } - index++; if (!reader.IsDBNull(index)) { @@ -1568,7 +1575,7 @@ namespace Emby.Server.Implementations.Data } index++; - if (query.HasField(ItemFields.Overview)) + if (HasField(query, ItemFields.Overview)) { if (!reader.IsDBNull(index)) { @@ -1595,7 +1602,7 @@ namespace Emby.Server.Implementations.Data } index++; - if (query.HasField(ItemFields.HomePageUrl)) + if (HasField(query, ItemFields.HomePageUrl)) { if (!reader.IsDBNull(index)) { @@ -1604,7 +1611,7 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.DisplayMediaType)) + if (HasField(query, ItemFields.DisplayMediaType)) { if (!reader.IsDBNull(index)) { @@ -1613,11 +1620,11 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.SortName)) + if (HasField(query, ItemFields.SortName)) { if (!reader.IsDBNull(index)) { - item.ForcedSortName = reader.GetString(index); + item.SortName = reader.GetString(index); } index++; } @@ -1628,7 +1635,7 @@ namespace Emby.Server.Implementations.Data } index++; - if (query.HasField(ItemFields.VoteCount)) + if (HasField(query, ItemFields.VoteCount)) { if (!reader.IsDBNull(index)) { @@ -1637,7 +1644,7 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.DateCreated)) + if (HasField(query, ItemFields.DateCreated)) { if (!reader.IsDBNull(index)) { @@ -1655,7 +1662,7 @@ namespace Emby.Server.Implementations.Data item.Id = reader.GetGuid(index); index++; - if (query.HasField(ItemFields.Genres)) + if (HasField(query, ItemFields.Genres)) { if (!reader.IsDBNull(index)) { @@ -1690,13 +1697,16 @@ namespace Emby.Server.Implementations.Data } index++; - if (!reader.IsDBNull(index)) + if (HasField(query, ItemFields.DateLastSaved)) { - item.DateLastSaved = reader[index].ReadDateTime(); + if (!reader.IsDBNull(index)) + { + item.DateLastSaved = reader[index].ReadDateTime(); + } + index++; } - index++; - if (query.HasField(ItemFields.Settings)) + if (HasField(query, ItemFields.Settings)) { if (!reader.IsDBNull(index)) { @@ -1705,7 +1715,7 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.Studios)) + if (HasField(query, ItemFields.Studios)) { if (!reader.IsDBNull(index)) { @@ -1714,7 +1724,7 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.Tags)) + if (HasField(query, ItemFields.Tags)) { if (!reader.IsDBNull(index)) { @@ -1729,17 +1739,20 @@ namespace Emby.Server.Implementations.Data } index++; - var trailer = item as Trailer; - if (trailer != null) + if (hasTrailerTypes) { - if (!reader.IsDBNull(index)) + var trailer = item as Trailer; + if (trailer != null) { - trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true)).ToList(); + if (!reader.IsDBNull(index)) + { + trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true)).ToList(); + } } + index++; } - index++; - if (query.HasField(ItemFields.OriginalTitle)) + if (HasField(query, ItemFields.OriginalTitle)) { if (!reader.IsDBNull(index)) { @@ -1758,7 +1771,7 @@ namespace Emby.Server.Implementations.Data } index++; - if (query.HasField(ItemFields.DateLastMediaAdded)) + if (HasField(query, ItemFields.DateLastMediaAdded)) { var folder = item as Folder; if (folder != null && !reader.IsDBNull(index)) @@ -1787,68 +1800,89 @@ namespace Emby.Server.Implementations.Data index++; var hasSeries = item as IHasSeries; - if (hasSeries != null) + if (hasSeriesFields) { - if (!reader.IsDBNull(index)) + if (hasSeries != null) { - hasSeries.SeriesName = reader.GetString(index); + if (!reader.IsDBNull(index)) + { + hasSeries.SeriesName = reader.GetString(index); + } } + index++; } - index++; - var episode = item as Episode; - if (episode != null) + if (hasEpisodeAttributes) { - if (!reader.IsDBNull(index)) + var episode = item as Episode; + if (episode != null) { - episode.SeasonName = reader.GetString(index); + if (!reader.IsDBNull(index)) + { + episode.SeasonName = reader.GetString(index); + } + index++; + if (!reader.IsDBNull(index)) + { + episode.SeasonId = reader.GetGuid(index); + } } - index++; - if (!reader.IsDBNull(index)) + else { - episode.SeasonId = reader.GetGuid(index); + index++; } + index++; } - else + + if (hasSeriesFields) { + if (hasSeries != null) + { + if (!reader.IsDBNull(index)) + { + hasSeries.SeriesId = reader.GetGuid(index); + } + } index++; } - index++; - if (hasSeries != null) + if (HasField(query, ItemFields.PresentationUniqueKey)) { if (!reader.IsDBNull(index)) { - hasSeries.SeriesId = reader.GetGuid(index); + item.PresentationUniqueKey = reader.GetString(index); } + index++; } - index++; - if (!reader.IsDBNull(index)) + if (HasField(query, ItemFields.InheritedParentalRatingValue)) { - item.PresentationUniqueKey = reader.GetString(index); + if (!reader.IsDBNull(index)) + { + item.InheritedParentalRatingValue = reader.GetInt32(index); + } + index++; } - index++; - if (!reader.IsDBNull(index)) + if (HasField(query, ItemFields.Tags)) { - item.InheritedParentalRatingValue = reader.GetInt32(index); - } - index++; - - if (!reader.IsDBNull(index)) - { - item.InheritedTags = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + if (!reader.IsDBNull(index)) + { + item.InheritedTags = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + index++; } - index++; - if (!reader.IsDBNull(index)) + if (HasField(query, ItemFields.ExternalSeriesId)) { - item.ExternalSeriesId = reader.GetString(index); + if (!reader.IsDBNull(index)) + { + item.ExternalSeriesId = reader.GetString(index); + } + index++; } - index++; - if (query.HasField(ItemFields.Taglines)) + if (HasField(query, ItemFields.Taglines)) { if (!reader.IsDBNull(index)) { @@ -1857,7 +1891,7 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.Keywords)) + if (HasField(query, ItemFields.Keywords)) { if (!reader.IsDBNull(index)) { @@ -1881,7 +1915,7 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.ProductionLocations)) + if (HasField(query, ItemFields.ProductionLocations)) { if (!reader.IsDBNull(index)) { @@ -1890,7 +1924,7 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.ThemeSongIds)) + if (HasField(query, ItemFields.ThemeSongIds)) { if (!reader.IsDBNull(index)) { @@ -1899,7 +1933,7 @@ namespace Emby.Server.Implementations.Data index++; } - if (query.HasField(ItemFields.ThemeVideoIds)) + if (HasField(query, ItemFields.ThemeVideoIds)) { if (!reader.IsDBNull(index)) { @@ -1920,19 +1954,22 @@ namespace Emby.Server.Implementations.Data } index++; - var hasArtists = item as IHasArtist; - if (hasArtists != null && !reader.IsDBNull(index)) + if (hasArtistFields) { - hasArtists.Artists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - } - index++; + var hasArtists = item as IHasArtist; + if (hasArtists != null && !reader.IsDBNull(index)) + { + hasArtists.Artists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + index++; - var hasAlbumArtists = item as IHasAlbumArtist; - if (hasAlbumArtists != null && !reader.IsDBNull(index)) - { - hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + var hasAlbumArtists = item as IHasAlbumArtist; + if (hasAlbumArtists != null && !reader.IsDBNull(index)) + { + hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + index++; } - index++; if (!reader.IsDBNull(index)) { @@ -1940,14 +1977,17 @@ namespace Emby.Server.Implementations.Data } index++; - if (hasSeries != null) + if (HasField(query, ItemFields.SeriesPresentationUniqueKey)) { - if (!reader.IsDBNull(index)) + if (hasSeries != null) { - hasSeries.SeriesPresentationUniqueKey = reader.GetString(index); + if (!reader.IsDBNull(index)) + { + hasSeries.SeriesPresentationUniqueKey = reader.GetString(index); + } } + index++; } - index++; return item; } @@ -2243,23 +2283,225 @@ namespace Emby.Server.Implementations.Data } if (field == ItemFields.SortName) { - return new[] { "ForcedSortName" }; + return new[] { "SortName" }; } if (field == ItemFields.Taglines) { return new[] { "Tagline" }; } + if (field == ItemFields.Tags) + { + return new[] { "Tags", "InheritedTags" }; + } return new[] { field.ToString() }; } + private bool HasField(InternalItemsQuery query, ItemFields name) + { + var fields = query.DtoOptions.Fields; + + switch (name) + { + case ItemFields.HomePageUrl: + case ItemFields.Keywords: + case ItemFields.DisplayMediaType: + case ItemFields.VoteCount: + case ItemFields.CustomRating: + case ItemFields.ProductionLocations: + case ItemFields.Settings: + case ItemFields.OriginalTitle: + case ItemFields.Taglines: + case ItemFields.SortName: + case ItemFields.Studios: + case ItemFields.Tags: + case ItemFields.ThemeSongIds: + case ItemFields.ThemeVideoIds: + case ItemFields.DateCreated: + case ItemFields.Overview: + case ItemFields.Genres: + case ItemFields.DateLastMediaAdded: + case ItemFields.ExternalEtag: + case ItemFields.PresentationUniqueKey: + case ItemFields.InheritedParentalRatingValue: + case ItemFields.ExternalSeriesId: + case ItemFields.SeriesPresentationUniqueKey: + case ItemFields.DateLastRefreshed: + case ItemFields.DateLastSaved: + return fields.Contains(name); + case ItemFields.ServiceName: + return true; + default: + return true; + } + } + + private bool HasProgramAttributes(InternalItemsQuery query) + { + var excludeParentTypes = new string[] + { + "Series", + "Season", + "MusicAlbum", + "MusicArtist", + "PhotoAlbum" + }; + + if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + var types = new string[] + { + "Program", + "Recording", + "TvChannel", + "LiveTvAudioRecording", + "LiveTvVideoRecording", + "LiveTvProgram", + "LiveTvTvChannel" + }; + + return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + } + + private bool HasStartDate(InternalItemsQuery query) + { + var excludeParentTypes = new string[] + { + "Series", + "Season", + "MusicAlbum", + "MusicArtist", + "PhotoAlbum" + }; + + if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + var types = new string[] + { + "Program", + "Recording", + "LiveTvAudioRecording", + "LiveTvVideoRecording", + "LiveTvProgram" + }; + + return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + } + + private bool HasEpisodeAttributes(InternalItemsQuery query) + { + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + var types = new string[] + { + "Episode" + }; + + return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + } + + private bool HasTrailerTypes(InternalItemsQuery query) + { + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + var types = new string[] + { + "Trailer" + }; + + return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + } + + private bool HasArtistFields(InternalItemsQuery query) + { + var excludeParentTypes = new string[] + { + "Series", + "Season", + "PhotoAlbum" + }; + + if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + var types = new string[] + { + "Audio", + "MusicAlbum", + "MusicVideo", + "AudioBook", + "AudioPodcast", + "LiveTvAudioRecording", + "Recording" + }; + + return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + } + + private bool HasSeriesFields(InternalItemsQuery query) + { + var excludeParentTypes = new string[] + { + "PhotoAlbum" + }; + + if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + var types = new string[] + { + "Book", + "AudioBook", + "Episode", + "Season" + }; + + return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + } + private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns) { var list = startColumns.ToList(); foreach (var field in allFields) { - if (!query.HasField(field)) + if (!HasField(query, field)) { foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList()) { @@ -2268,6 +2510,53 @@ namespace Emby.Server.Implementations.Data } } + if (!HasProgramAttributes(query)) + { + list.Remove("IsKids"); + list.Remove("IsMovie"); + list.Remove("IsSports"); + list.Remove("IsSeries"); + list.Remove("IsLive"); + list.Remove("IsNews"); + list.Remove("IsPremiere"); + list.Remove("EpisodeTitle"); + list.Remove("IsRepeat"); + } + + if (!HasEpisodeAttributes(query)) + { + list.Remove("SeasonName"); + list.Remove("SeasonId"); + } + + if (!HasStartDate(query)) + { + list.Remove("StartDate"); + } + + if (!HasTrailerTypes(query)) + { + list.Remove("TrailerTypes"); + } + + if (!HasArtistFields(query)) + { + list.Remove("AlbumArtists"); + list.Remove("Artists"); + } + + if (!HasSeriesFields(query)) + { + list.Remove("SeriesId"); + list.Remove("SeriesName"); + } + + if (!HasEpisodeAttributes(query)) + { + list.Remove("SeasonName"); + list.Remove("SeasonId"); + } + if (!query.DtoOptions.EnableImages) { list.Remove("Images"); @@ -2394,7 +2683,7 @@ namespace Emby.Server.Implementations.Data query.Limit = query.Limit.Value + 4; } - var commandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new [] { "count(distinct PresentationUniqueKey)" })) + GetFromText(); + var commandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) + GetFromText(); commandText += GetJoinUserDataText(query); var whereClauses = GetWhereClauses(query, null); @@ -2499,9 +2788,16 @@ namespace Emby.Server.Implementations.Data // Running this again will bind the params GetWhereClauses(query, statement); + var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasProgramAttributes = HasProgramAttributes(query); + var hasStartDate = HasStartDate(query); + var hasTrailerTypes = HasTrailerTypes(query); + var hasArtistFields = HasArtistFields(query); + var hasSeriesFields = HasSeriesFields(query); + foreach (var row in statement.ExecuteQuery()) { - var item = GetItem(row, query); + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); if (item != null) { list.Add(item); @@ -2701,9 +2997,16 @@ namespace Emby.Server.Implementations.Data // Running this again will bind the params GetWhereClauses(query, statement); + var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasProgramAttributes = HasProgramAttributes(query); + var hasStartDate = HasStartDate(query); + var hasTrailerTypes = HasTrailerTypes(query); + var hasArtistFields = HasArtistFields(query); + var hasSeriesFields = HasSeriesFields(query); + foreach (var row in statement.ExecuteQuery()) { - var item = GetItem(row, query); + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); if (item != null) { list.Add(item); @@ -3471,6 +3774,15 @@ namespace Emby.Server.Implementations.Data } } + if (query.MinDateLastSavedForUser.HasValue) + { + whereClauses.Add("DateLastSaved>=@MinDateLastSaved"); + if (statement != null) + { + statement.TryBind("@MinDateLastSaved", query.MinDateLastSavedForUser.Value); + } + } + //if (query.MinPlayers.HasValue) //{ // whereClauses.Add("Players>=@MinPlayers"); @@ -3665,10 +3977,10 @@ namespace Emby.Server.Implementations.Data if (!string.IsNullOrWhiteSpace(query.SlugName)) { - whereClauses.Add("SlugName=@SlugName"); + whereClauses.Add("CleanName=@SlugName"); if (statement != null) { - statement.TryBind("@SlugName", query.SlugName); + statement.TryBind("@SlugName", GetCleanValue(query.SlugName)); } } @@ -4919,7 +5231,9 @@ namespace Emby.Server.Implementations.Data var columns = _retriveItemColumns.ToList(); columns.AddRange(itemCountColumns.Select(i => i.Item2).ToArray()); - var commandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, columns.ToArray())) + GetFromText(); + columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList(); + + var commandText = "select " + string.Join(",", columns.ToArray()) + GetFromText(); commandText += GetJoinUserDataText(query); var innerQuery = new InternalItemsQuery(query.User) @@ -4962,7 +5276,6 @@ namespace Emby.Server.Implementations.Data NameLessThan = query.NameLessThan, NameStartsWith = query.NameStartsWith, NameStartsWithOrGreater = query.NameStartsWithOrGreater, - AlbumArtistStartsWithOrGreater = query.AlbumArtistStartsWithOrGreater, Tags = query.Tags, OfficialRatings = query.OfficialRatings, GenreIds = query.GenreIds, @@ -5043,9 +5356,16 @@ namespace Emby.Server.Implementations.Data GetWhereClauses(innerQuery, statement); GetWhereClauses(outerQuery, statement); + var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasProgramAttributes = HasProgramAttributes(query); + var hasStartDate = HasStartDate(query); + var hasTrailerTypes = HasTrailerTypes(query); + var hasArtistFields = HasArtistFields(query); + var hasSeriesFields = HasSeriesFields(query); + foreach (var row in statement.ExecuteQuery()) { - var item = GetItem(row); + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); if (item != null) { var countStartColumn = columns.Count - 1; diff --git a/Emby.Server.Implementations/Devices/CameraUploadsDynamicFolder.cs b/Emby.Server.Implementations/Devices/CameraUploadsDynamicFolder.cs index e2d5d0272..52979f085 100644 --- a/Emby.Server.Implementations/Devices/CameraUploadsDynamicFolder.cs +++ b/Emby.Server.Implementations/Devices/CameraUploadsDynamicFolder.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Providers; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index b246ef196..027a55516 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -16,7 +16,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.IO; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index d784bcb09..45fbd69dc 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -24,7 +24,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Extensions; @@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Dto foreach (var item in items) { - var dto = await GetBaseItemDtoInternal(item, options, user, owner).ConfigureAwait(false); + var dto = GetBaseItemDtoInternal(item, options, user, owner); var tvChannel = item as LiveTvChannel; if (tvChannel != null) @@ -127,7 +127,11 @@ namespace Emby.Server.Implementations.Dto { var libraryItems = byName.GetTaggedItems(new InternalItemsQuery(user) { - Recursive = true + Recursive = true, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } }); SetItemByNameInfo(item, dto, libraryItems.ToList(), user); @@ -156,7 +160,7 @@ namespace Emby.Server.Implementations.Dto { var syncDictionary = GetSyncedItemProgress(options); - var dto = GetBaseItemDtoInternal(item, options, user, owner).Result; + var dto = GetBaseItemDtoInternal(item, options, user, owner); var tvChannel = item as LiveTvChannel; if (tvChannel != null) { @@ -177,7 +181,11 @@ namespace Emby.Server.Implementations.Dto { if (options.Fields.Contains(ItemFields.ItemCounts)) { - SetItemByNameInfo(item, dto, GetTaggedItems(byName, user), user); + SetItemByNameInfo(item, dto, GetTaggedItems(byName, user, new DtoOptions(false) + { + EnableImages = false + + }), user); } FillSyncInfo(dto, item, options, user, syncDictionary); @@ -189,11 +197,12 @@ namespace Emby.Server.Implementations.Dto return dto; } - private List<BaseItem> GetTaggedItems(IItemByName byName, User user) + private List<BaseItem> GetTaggedItems(IItemByName byName, User user, DtoOptions options) { var items = byName.GetTaggedItems(new InternalItemsQuery(user) { - Recursive = true + Recursive = true, + DtoOptions = options }).ToList(); @@ -283,7 +292,7 @@ namespace Emby.Server.Implementations.Dto } } - private async Task<BaseItemDto> GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) + private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { var fields = options.Fields; @@ -332,7 +341,7 @@ namespace Emby.Server.Implementations.Dto if (user != null) { - await AttachUserSpecificInfo(dto, item, user, options).ConfigureAwait(false); + AttachUserSpecificInfo(dto, item, user, options); } var hasMediaSources = item as IHasMediaSources; @@ -393,7 +402,7 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Dictionary<string, SyncedItemProgress> syncProgress, User user = null) { - var dto = GetBaseItemDtoInternal(item, options, user).Result; + var dto = GetBaseItemDtoInternal(item, options, user); if (taggedItems != null && options.Fields.Contains(ItemFields.ItemCounts)) { @@ -446,7 +455,7 @@ namespace Emby.Server.Implementations.Dto /// <summary> /// Attaches the user specific info. /// </summary> - private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions) + private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions) { var fields = dtoOptions.Fields; @@ -456,7 +465,7 @@ namespace Emby.Server.Implementations.Dto if (dtoOptions.EnableUserData) { - dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user, dtoOptions.Fields).ConfigureAwait(false); + dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, dtoOptions.Fields); } if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library) @@ -488,7 +497,7 @@ namespace Emby.Server.Implementations.Dto { if (dtoOptions.EnableUserData) { - dto.UserData = await _userDataRepository.GetUserDataDto(item, user).ConfigureAwait(false); + dto.UserData = _userDataRepository.GetUserDataDto(item, user); } } @@ -595,16 +604,17 @@ namespace Emby.Server.Implementations.Dto { if (!string.IsNullOrEmpty(item.Album)) { - var parentAlbum = _libraryManager.GetItemList(new InternalItemsQuery + var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, - Name = item.Album + Name = item.Album, + Limit = 1 - }).FirstOrDefault(); + }); - if (parentAlbum != null) + if (parentAlbumIds.Count > 0) { - dto.AlbumId = GetDtoId(parentAlbum); + dto.AlbumId = parentAlbumIds[0].ToString("N"); } } @@ -751,45 +761,41 @@ namespace Emby.Server.Implementations.Dto /// <returns>Task.</returns> private void AttachStudios(BaseItemDto dto, BaseItem item) { - var studios = item.Studios.ToList(); - - dto.Studios = new StudioDto[studios.Count]; - - var dictionary = studios.Distinct(StringComparer.OrdinalIgnoreCase).Select(name => - { - try - { - return _libraryManager.GetStudio(name); - } - catch (IOException ex) + dto.Studios = item.Studios + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => new NameIdPair { - _logger.ErrorException("Error getting studio {0}", ex, name); - return null; - } - }) - .Where(i => i != null) - .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) - .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); - - for (var i = 0; i < studios.Count; i++) - { - var studio = studios[i]; + Name = i, + Id = _libraryManager.GetStudioId(i).ToString("N") + }) + .ToArray(); + } - var studioDto = new StudioDto + private void AttachGenreItems(BaseItemDto dto, BaseItem item) + { + dto.GenreItems = item.Genres + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => new NameIdPair { - Name = studio - }; - - Studio entity; + Name = i, + Id = GetStudioId(i, item) + }) + .ToArray(); + } - if (dictionary.TryGetValue(studio, out entity)) - { - studioDto.Id = entity.Id.ToString("N"); - studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); - } + private string GetStudioId(string name, BaseItem owner) + { + if (owner is IHasMusicGenres) + { + return _libraryManager.GetGameGenreId(name).ToString("N"); + } - dto.Studios[i] = studioDto; + if (owner is Game || owner is GameSystem) + { + return _libraryManager.GetGameGenreId(name).ToString("N"); } + + return _libraryManager.GetGenreId(name).ToString("N"); } /// <summary> @@ -901,6 +907,7 @@ namespace Emby.Server.Implementations.Dto if (fields.Contains(ItemFields.Genres)) { dto.Genres = item.Genres; + AttachGenreItems(dto, item); } if (options.EnableImages) @@ -1130,7 +1137,10 @@ namespace Emby.Server.Implementations.Dto return null; } - var artist = _libraryManager.GetArtist(i); + var artist = _libraryManager.GetArtist(i, new DtoOptions(false) + { + EnableImages = false + }); if (artist != null) { return new NameIdPair @@ -1179,7 +1189,10 @@ namespace Emby.Server.Implementations.Dto return null; } - var artist = _libraryManager.GetArtist(i); + var artist = _libraryManager.GetArtist(i, new DtoOptions(false) + { + EnableImages = false + }); if (artist != null) { return new NameIdPair @@ -1449,7 +1462,7 @@ namespace Emby.Server.Implementations.Dto var musicAlbum = item as MusicAlbum; if (musicAlbum != null) { - var artist = musicAlbum.MusicArtist; + var artist = musicAlbum.GetMusicArtist(new DtoOptions(false)); if (artist != null) { return artist; @@ -1582,24 +1595,30 @@ namespace Emby.Server.Implementations.Dto ImageSize size; - if (supportedEnhancers.Count == 0) - { - var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio(); + var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio(); - if (defaultAspectRatio.HasValue) + if (defaultAspectRatio.HasValue) + { + if (supportedEnhancers.Count == 0) { return defaultAspectRatio.Value; } - } - try - { - size = _imageProcessor.GetImageSize(imageInfo); + double dummyWidth = 200; + double dummyHeight = dummyWidth / defaultAspectRatio.Value; + size = new ImageSize(dummyWidth, dummyHeight); } - catch + else { - //_logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path); - return null; + try + { + size = _imageProcessor.GetImageSize(imageInfo); + } + catch + { + //_logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path); + return null; + } } foreach (var enhancer in supportedEnhancers) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index b2f1f0ceb..14d976325 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -10,9 +10,8 @@ <RootNamespace>Emby.Server.Implementations</RootNamespace> <AssemblyName>Emby.Server.Implementations</AssemblyName> <FileAlignment>512</FileAlignment> - <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> - <TargetFrameworkProfile>Profile7</TargetFrameworkProfile> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkProfile /> + <TargetFrameworkVersion>v4.6</TargetFrameworkVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -103,6 +102,7 @@ <Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" /> <Compile Include="HttpServer\StreamWriter.cs" /> <Compile Include="Images\BaseDynamicImageProvider.cs" /> + <Compile Include="IO\AsyncStreamCopier.cs" /> <Compile Include="IO\FileRefresher.cs" /> <Compile Include="IO\MbLinkShortcutHandler.cs" /> <Compile Include="IO\ThrottledStream.cs" /> @@ -182,7 +182,6 @@ <Compile Include="Migrations\IVersionMigration.cs" /> <Compile Include="Migrations\LibraryScanMigration.cs" /> <Compile Include="Migrations\GuideMigration.cs" /> - <Compile Include="Migrations\UpdateLevelMigration.cs" /> <Compile Include="News\NewsEntryPoint.cs" /> <Compile Include="News\NewsService.cs" /> <Compile Include="Notifications\CoreNotificationTypes.cs" /> @@ -291,9 +290,9 @@ <Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project> <Name>MediaBrowser.Server.Implementations</Name> </ProjectReference> - <ProjectReference Include="..\SocketHttpListener.Portable\SocketHttpListener.Portable.csproj"> - <Project>{4f26d5d8-a7b0-42b3-ba42-7cb7d245934e}</Project> - <Name>SocketHttpListener.Portable</Name> + <ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj"> + <Project>{1d74413b-e7cf-455b-b021-f52bdf881542}</Project> + <Name>SocketHttpListener</Name> </ProjectReference> <Reference Include="Emby.XmlTv, Version=1.0.6299.28292, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\Emby.XmlTv.1.0.8\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath> @@ -313,6 +312,17 @@ </Reference> </ItemGroup> <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Runtime.Serialization" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> <EmbeddedResource Include="Localization\Core\ar.json" /> <EmbeddedResource Include="Localization\Core\bg-BG.json" /> <EmbeddedResource Include="Localization\Core\ca.json" /> @@ -410,7 +420,7 @@ <ItemGroup> <EmbeddedResource Include="Localization\Ratings\uk.txt" /> </ItemGroup> - <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index b93410180..71e31d4d4 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.EntryPoints .DistinctBy(i => i.Id) .Select(i => { - var dto = _userDataManager.GetUserDataDto(i, user).Result; + var dto = _userDataManager.GetUserDataDto(i, user); dto.ItemId = i.Id.ToString("N"); return dto; }) diff --git a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 86c8c5f68..cf9fdbb16 100644 --- a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -15,7 +15,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; -using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; @@ -588,7 +588,8 @@ namespace Emby.Server.Implementations.FileOrganization var series = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Series).Name }, - Recursive = true + Recursive = true, + DtoOptions = new DtoOptions(true) }) .Cast<Series>() .Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i)) @@ -607,7 +608,8 @@ namespace Emby.Server.Implementations.FileOrganization { IncludeItemTypes = new[] { typeof(Series).Name }, Recursive = true, - Name = info.ItemName + Name = info.ItemName, + DtoOptions = new DtoOptions(true) }).Cast<Series>().FirstOrDefault(); } diff --git a/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs b/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs index 4094e6b9b..d95bd8734 100644 --- a/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ b/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs @@ -17,7 +17,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Common.Events; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 5be7ba7ad..b71a3975f 100644 --- a/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs index 2cbf2613e..0dbd6f837 100644 --- a/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ b/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs @@ -10,7 +10,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 5e96eda94..79209d438 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text; +using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; @@ -445,10 +446,7 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Overridable method that can be used to implement a custom hnandler /// </summary> - /// <param name="httpReq">The HTTP req.</param> - /// <param name="url">The URL.</param> - /// <returns>Task.</returns> - protected async Task RequestHandler(IHttpRequest httpReq, Uri url) + protected async Task RequestHandler(IHttpRequest httpReq, Uri url, CancellationToken cancellationToken) { var date = DateTime.Now; var httpRes = httpReq.Response; @@ -589,7 +587,7 @@ namespace Emby.Server.Implementations.HttpServer if (handler != null) { - await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName).ConfigureAwait(false); + await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false); } else { diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 0af88595f..6c37d5f7a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -58,6 +58,18 @@ namespace Emby.Server.Implementations.HttpServer return GetHttpResult(content, contentType, true, responseHeaders); } + public object GetRedirectResult(string url) + { + var responseHeaders = new Dictionary<string, string>(); + responseHeaders["Location"] = url; + + var result = new HttpResult(new byte[] { }, "text/plain", HttpStatusCode.Redirect); + + AddResponseHeaders(result, responseHeaders); + + return result; + } + /// <summary> /// Gets the HTTP result. /// </summary> @@ -599,9 +611,9 @@ namespace Emby.Server.Implementations.HttpServer } } - private async Task<IHasHeaders> GetCompressedResult(Stream stream, - string requestedCompressionType, - IDictionary<string,string> responseHeaders, + private async Task<IHasHeaders> GetCompressedResult(Stream stream, + string requestedCompressionType, + IDictionary<string, string> responseHeaders, bool isHeadRequest, string contentType) { diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 18df5682d..82175dbed 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Net; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; @@ -18,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer /// Gets or sets the request handler. /// </summary> /// <value>The request handler.</value> - Func<IHttpRequest, Uri, Task> RequestHandler { get; set; } + Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; } /// <summary> /// Gets or sets the web socket handler. diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index 682fa7a0b..e648838b2 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -4,6 +4,7 @@ using SocketHttpListener.Net; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Model.Cryptography; @@ -33,6 +34,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private readonly bool _enableDualMode; private readonly IEnvironmentInfo _environment; + private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); + private CancellationToken _disposeCancellationToken; + public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem, IEnvironmentInfo environment) { _logger = logger; @@ -47,10 +51,12 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _httpRequestFactory = httpRequestFactory; _fileSystem = fileSystem; _environment = environment; + + _disposeCancellationToken = _disposeCancellationTokenSource.Token; } public Action<Exception, IRequest, bool> ErrorHandler { get; set; } - public Func<IHttpRequest, Uri, Task> RequestHandler { get; set; } + public Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; } public Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; } @@ -81,11 +87,11 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private void ProcessContext(HttpListenerContext context) { - //Task.Factory.StartNew(() => InitTask(context), TaskCreationOptions.DenyChildAttach | TaskCreationOptions.PreferFairness); - Task.Run(() => InitTask(context)); + InitTask(context, _disposeCancellationToken); + //Task.Run(() => InitTask(context, _disposeCancellationToken)); } - private Task InitTask(HttpListenerContext context) + private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) { IHttpRequest httpReq = null; var request = context.Request; @@ -111,7 +117,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return Task.FromResult(true); } - return RequestHandler(httpReq, request.Url); + return RequestHandler(httpReq, request.Url, cancellationToken); } private void ProcessWebSocketRequest(HttpListenerContext ctx) @@ -172,6 +178,8 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Stop() { + _disposeCancellationTokenSource.Cancel(); + if (_listener != null) { foreach (var prefix in _listener.Prefixes.ToList()) diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index b3fcde745..2dfe6a9e3 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; using System.Text; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; @@ -374,7 +373,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp this.pathInfo = request.RawUrl; } - this.pathInfo = WebUtility.UrlDecode(pathInfo); + this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); this.pathInfo = NormalizePathInfo(pathInfo, mode); } return this.pathInfo; @@ -440,7 +439,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp cookies = new Dictionary<string, System.Net.Cookie>(); foreach (var cookie in this.request.Cookies) { - var httpCookie = (Cookie) cookie; + var httpCookie = (System.Net.Cookie) cookie; cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain); } } diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index 9e58ee57c..d6762d94b 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -114,15 +114,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp var outputStream = response.OutputStream; // This is needed with compression - if (outputStream is ResponseStream) - { - //if (!string.IsNullOrWhiteSpace(GetHeader("Content-Encoding"))) - { - outputStream.Flush(); - } + outputStream.Flush(); + outputStream.Dispose(); - outputStream.Dispose(); - } response.Close(); } catch (Exception ex) diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 33378949c..5d42f42fa 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -5,7 +5,7 @@ using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.HttpServer diff --git a/Emby.Server.Implementations/IO/AsyncStreamCopier.cs b/Emby.Server.Implementations/IO/AsyncStreamCopier.cs new file mode 100644 index 000000000..9e5ce0604 --- /dev/null +++ b/Emby.Server.Implementations/IO/AsyncStreamCopier.cs @@ -0,0 +1,459 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Emby.Server.Implementations.IO +{ + public class AsyncStreamCopier : IDisposable + { + // size in bytes of the buffers in the buffer pool + private const int DefaultBufferSize = 81920; + private readonly int _bufferSize; + // number of buffers in the pool + private const int DefaultBufferCount = 4; + private readonly int _bufferCount; + + // indexes of the next buffer to read into/write from + private int _nextReadBuffer = -1; + private int _nextWriteBuffer = -1; + + // the buffer pool, implemented as an array, and used in a cyclic way + private readonly byte[][] _buffers; + // sizes in bytes of the available (read) data in the buffers + private readonly int[] _sizes; + // the streams... + private Stream _source; + private Stream _target; + private readonly bool _closeStreamsOnEnd; + + // number of buffers that are ready to be written + private int _buffersToWrite; + // flag indicating that there is still a read operation to be scheduled + // (source end of stream not reached) + private volatile bool _moreDataToRead; + // the result of the whole operation, returned by BeginCopy() + private AsyncResult _asyncResult; + // any exception that occurs during an async operation + // stored here for rethrow + private Exception _exception; + + public TaskCompletionSource<long> TaskCompletionSource; + private long _bytesToRead; + private long _totalBytesWritten; + private CancellationToken _cancellationToken; + public int IndividualReadOffset = 0; + + public AsyncStreamCopier(Stream source, + Stream target, + long bytesToRead, + CancellationToken cancellationToken, + bool closeStreamsOnEnd = false, + int bufferSize = DefaultBufferSize, + int bufferCount = DefaultBufferCount) + { + if (source == null) + throw new ArgumentNullException("source"); + if (target == null) + throw new ArgumentNullException("target"); + if (!source.CanRead) + throw new ArgumentException("Cannot copy from a non-readable stream."); + if (!target.CanWrite) + throw new ArgumentException("Cannot copy to a non-writable stream."); + _source = source; + _target = target; + _moreDataToRead = true; + _closeStreamsOnEnd = closeStreamsOnEnd; + _bufferSize = bufferSize; + _bufferCount = bufferCount; + _buffers = new byte[_bufferCount][]; + _sizes = new int[_bufferCount]; + _bytesToRead = bytesToRead; + _cancellationToken = cancellationToken; + } + + ~AsyncStreamCopier() + { + // ensure any exception cannot be ignored + ThrowExceptionIfNeeded(); + } + + public static Task<long> CopyStream(Stream source, Stream target, int bufferSize, int bufferCount, CancellationToken cancellationToken) + { + return CopyStream(source, target, 0, bufferSize, bufferCount, cancellationToken); + } + + public static Task<long> CopyStream(Stream source, Stream target, long size, int bufferSize, int bufferCount, CancellationToken cancellationToken) + { + var copier = new AsyncStreamCopier(source, target, size, cancellationToken, false, bufferSize, bufferCount); + var taskCompletion = new TaskCompletionSource<long>(); + + copier.TaskCompletionSource = taskCompletion; + + var result = copier.BeginCopy(StreamCopyCallback, copier); + + if (result.CompletedSynchronously) + { + StreamCopyCallback(result); + } + + cancellationToken.Register(() => taskCompletion.TrySetCanceled()); + + return taskCompletion.Task; + } + + private static void StreamCopyCallback(IAsyncResult result) + { + var copier = (AsyncStreamCopier)result.AsyncState; + var taskCompletion = copier.TaskCompletionSource; + + try + { + copier.EndCopy(result); + taskCompletion.TrySetResult(copier._totalBytesWritten); + } + catch (Exception ex) + { + taskCompletion.TrySetException(ex); + } + } + + public void Dispose() + { + if (_asyncResult != null) + _asyncResult.Dispose(); + if (_closeStreamsOnEnd) + { + if (_source != null) + { + _source.Dispose(); + _source = null; + } + if (_target != null) + { + _target.Dispose(); + _target = null; + } + } + GC.SuppressFinalize(this); + ThrowExceptionIfNeeded(); + } + + public IAsyncResult BeginCopy(AsyncCallback callback, object state) + { + // avoid concurrent start of the copy on separate threads + if (Interlocked.CompareExchange(ref _asyncResult, new AsyncResult(callback, state), null) != null) + throw new InvalidOperationException("A copy operation has already been started on this object."); + // allocate buffers + for (int i = 0; i < _bufferCount; i++) + _buffers[i] = new byte[_bufferSize]; + + // we pass false to BeginRead() to avoid completing the async result + // immediately which would result in invoking the callback + // when the method fails synchronously + BeginRead(false); + // throw exception synchronously if there is one + ThrowExceptionIfNeeded(); + return _asyncResult; + } + + public void EndCopy(IAsyncResult ar) + { + if (ar != _asyncResult) + throw new InvalidOperationException("Invalid IAsyncResult object."); + + if (!_asyncResult.IsCompleted) + _asyncResult.AsyncWaitHandle.WaitOne(); + + if (_closeStreamsOnEnd) + { + _source.Close(); + _source = null; + _target.Close(); + _target = null; + } + + //_logger.Info("AsyncStreamCopier {0} bytes requested. {1} bytes transferred", _bytesToRead, _totalBytesWritten); + ThrowExceptionIfNeeded(); + } + + /// <summary> + /// Here we'll throw a pending exception if there is one, + /// and remove it from our instance, so we know it has been consumed. + /// </summary> + private void ThrowExceptionIfNeeded() + { + if (_exception != null) + { + var exception = _exception; + _exception = null; + throw exception; + } + } + + private void BeginRead(bool completeOnError = true) + { + if (!_moreDataToRead) + { + return; + } + if (_asyncResult.IsCompleted) + return; + int bufferIndex = Interlocked.Increment(ref _nextReadBuffer) % _bufferCount; + + try + { + _source.BeginRead(_buffers[bufferIndex], 0, _bufferSize, EndRead, bufferIndex); + } + catch (Exception exception) + { + _exception = exception; + if (completeOnError) + _asyncResult.Complete(false); + } + } + + private void BeginWrite() + { + if (_asyncResult.IsCompleted) + return; + // this method can actually be called concurrently!! + // indeed, let's say we call a BeginWrite, and the thread gets interrupted + // just after making the IO request. + // At that moment, the thread is still in the method. And then the IO request + // ends (extremely fast io, or caching...), EndWrite gets called + // on another thread, and calls BeginWrite again! There we have it! + // That is the reason why an Interlocked is needed here. + int bufferIndex = Interlocked.Increment(ref _nextWriteBuffer) % _bufferCount; + + try + { + int bytesToWrite; + if (_bytesToRead > 0) + { + var bytesLeftToWrite = _bytesToRead - _totalBytesWritten; + bytesToWrite = Convert.ToInt32(Math.Min(_sizes[bufferIndex], bytesLeftToWrite)); + } + else + { + bytesToWrite = _sizes[bufferIndex]; + } + + _target.BeginWrite(_buffers[bufferIndex], IndividualReadOffset, bytesToWrite - IndividualReadOffset, EndWrite, null); + + _totalBytesWritten += bytesToWrite; + } + catch (Exception exception) + { + _exception = exception; + _asyncResult.Complete(false); + } + } + + private void EndRead(IAsyncResult ar) + { + try + { + int read = _source.EndRead(ar); + _moreDataToRead = read > 0; + var bufferIndex = (int)ar.AsyncState; + _sizes[bufferIndex] = read; + } + catch (Exception exception) + { + _exception = exception; + _asyncResult.Complete(false); + return; + } + + if (_moreDataToRead && !_cancellationToken.IsCancellationRequested) + { + int usedBuffers = Interlocked.Increment(ref _buffersToWrite); + // if we incremented from zero to one, then it means we just + // added the single buffer to write, so a writer could not + // be busy, and we have to schedule one. + if (usedBuffers == 1) + BeginWrite(); + // test if there is at least a free buffer, and schedule + // a read, as we have read some data + if (usedBuffers < _bufferCount) + BeginRead(); + } + else + { + // we did not add a buffer, because no data was read, and + // there is no buffer left to write so this is the end... + if (Thread.VolatileRead(ref _buffersToWrite) == 0) + { + _asyncResult.Complete(false); + } + } + } + + private void EndWrite(IAsyncResult ar) + { + try + { + _target.EndWrite(ar); + } + catch (Exception exception) + { + _exception = exception; + _asyncResult.Complete(false); + return; + } + + int buffersLeftToWrite = Interlocked.Decrement(ref _buffersToWrite); + // no reader could be active if all buffers were full of data waiting to be written + bool noReaderIsBusy = buffersLeftToWrite == _bufferCount - 1; + // note that it is possible that both a reader and + // a writer see the end of the copy and call Complete + // on the _asyncResult object. That race condition is handled by + // Complete that ensures it is only executed fully once. + + long bytesLeftToWrite; + if (_bytesToRead > 0) + { + bytesLeftToWrite = _bytesToRead - _totalBytesWritten; + } + else + { + bytesLeftToWrite = 1; + } + + if (!_moreDataToRead || bytesLeftToWrite <= 0 || _cancellationToken.IsCancellationRequested) + { + // at this point we know no reader can schedule a read or write + if (Thread.VolatileRead(ref _buffersToWrite) == 0) + { + // nothing left to write, so it is the end + _asyncResult.Complete(false); + return; + } + } + else + // here, we know we have something left to read, + // so schedule a read if no read is busy + if (noReaderIsBusy) + BeginRead(); + + // also schedule a write if we are sure we did not write the last buffer + // note that if buffersLeftToWrite is zero and a reader has put another + // buffer to write between the time we decremented _buffersToWrite + // and now, that reader will also schedule another write, + // as it will increment _buffersToWrite from zero to one + if (buffersLeftToWrite > 0) + BeginWrite(); + } + } + + internal class AsyncResult : IAsyncResult, IDisposable + { + // Fields set at construction which never change while + // operation is pending + private readonly AsyncCallback _asyncCallback; + private readonly object _asyncState; + + // Fields set at construction which do change after + // operation completes + private const int StatePending = 0; + private const int StateCompletedSynchronously = 1; + private const int StateCompletedAsynchronously = 2; + private int _completedState = StatePending; + + // Field that may or may not get set depending on usage + private ManualResetEvent _waitHandle; + + internal AsyncResult( + AsyncCallback asyncCallback, + object state) + { + _asyncCallback = asyncCallback; + _asyncState = state; + } + + internal bool Complete(bool completedSynchronously) + { + bool result = false; + + // The _completedState field MUST be set prior calling the callback + int prevState = Interlocked.CompareExchange(ref _completedState, + completedSynchronously ? StateCompletedSynchronously : + StateCompletedAsynchronously, StatePending); + if (prevState == StatePending) + { + // If the event exists, set it + if (_waitHandle != null) + _waitHandle.Set(); + + if (_asyncCallback != null) + _asyncCallback(this); + + result = true; + } + + return result; + } + + #region Implementation of IAsyncResult + + public Object AsyncState { get { return _asyncState; } } + + public bool CompletedSynchronously + { + get + { + return Thread.VolatileRead(ref _completedState) == + StateCompletedSynchronously; + } + } + + public WaitHandle AsyncWaitHandle + { + get + { + if (_waitHandle == null) + { + bool done = IsCompleted; + var mre = new ManualResetEvent(done); + if (Interlocked.CompareExchange(ref _waitHandle, + mre, null) != null) + { + // Another thread created this object's event; dispose + // the event we just created + mre.Close(); + } + else + { + if (!done && IsCompleted) + { + // If the operation wasn't done when we created + // the event but now it is done, set the event + _waitHandle.Set(); + } + } + } + return _waitHandle; + } + } + + public bool IsCompleted + { + get + { + return Thread.VolatileRead(ref _completedState) != + StatePending; + } + } + #endregion + + public void Dispose() + { + if (_waitHandle != null) + { + _waitHandle.Dispose(); + _waitHandle = null; + } + } + } +} diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index edff25156..9606b60b8 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Common.Events; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; @@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.IO try { - await item.ChangedExternally().ConfigureAwait(false); + item.ChangedExternally(); } catch (IOException ex) { @@ -231,7 +231,7 @@ namespace Emby.Server.Implementations.IO private bool IsFileLocked(string path) { - if (_environmentInfo.OperatingSystem != OperatingSystem.Windows) + if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) { // Causing lockups on linux return false; @@ -282,11 +282,11 @@ namespace Emby.Server.Implementations.IO return false; } } - //catch (DirectoryNotFoundException) - //{ - // // File may have been deleted - // return false; - //} + catch (DirectoryNotFoundException) + { + // File may have been deleted + return false; + } catch (FileNotFoundException) { // File may have been deleted diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs index 0b1391ae0..aef53751e 100644 --- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -1,6 +1,6 @@ using System; using System.IO; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index b2ec84a82..b8ce23a42 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -12,7 +12,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.IO; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.IO; @@ -59,33 +59,6 @@ namespace Emby.Server.Implementations.Images //return GetSupportedImages(item).Where(i => IsEnabled(options, i, item)).ToList(); } - private bool IsEnabled(MetadataOptions options, ImageType type, IHasImages item) - { - if (type == ImageType.Backdrop) - { - if (item.LockedFields.Contains(MetadataFields.Backdrops)) - { - return false; - } - } - else if (type == ImageType.Screenshot) - { - if (item.LockedFields.Contains(MetadataFields.Screenshots)) - { - return false; - } - } - else - { - if (item.LockedFields.Contains(MetadataFields.Images)) - { - return false; - } - } - - return options.IsEnabled(type); - } - public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) { if (!Supports(item)) @@ -128,7 +101,7 @@ namespace Emby.Server.Implementations.Images } } - var items = await GetItemsWithImages(item).ConfigureAwait(false); + var items = GetItemsWithImages(item); return await FetchToFileInternal(item, items, imageType, cancellationToken).ConfigureAwait(false); } @@ -140,7 +113,7 @@ namespace Emby.Server.Implementations.Images { var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPathWithoutExtension)); - string outputPath = await CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0).ConfigureAwait(false); + string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0); if (string.IsNullOrWhiteSpace(outputPath)) { @@ -159,9 +132,9 @@ namespace Emby.Server.Implementations.Images return ItemUpdateType.ImageUpdate; } - protected abstract Task<List<BaseItem>> GetItemsWithImages(IHasImages item); + protected abstract List<BaseItem> GetItemsWithImages(IHasImages item); - protected Task<string> CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath) + protected string CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 640, 360); } @@ -188,22 +161,22 @@ namespace Emby.Server.Implementations.Images .Where(i => !string.IsNullOrWhiteSpace(i)); } - protected Task<string> CreatePosterCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath) + protected string CreatePosterCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 400, 600); } - protected Task<string> CreateSquareCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath) + protected string CreateSquareCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 600, 600); } - protected Task<string> CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height) + protected string CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height) { return CreateCollage(primaryItem, items, outputPath, width, height); } - private async Task<string> CreateCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height) + private string CreateCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height) { FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath)); @@ -225,7 +198,7 @@ namespace Emby.Server.Implementations.Images return null; } - await ImageProcessor.CreateImageCollage(options).ConfigureAwait(false); + ImageProcessor.CreateImageCollage(options); return outputPath; } @@ -234,7 +207,7 @@ namespace Emby.Server.Implementations.Images get { return "Dynamic Image Provider"; } } - protected virtual async Task<string> CreateImage(IHasImages item, + protected virtual string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, @@ -249,20 +222,20 @@ namespace Emby.Server.Implementations.Images if (imageType == ImageType.Thumb) { - return await CreateThumbCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); + return CreateThumbCollage(item, itemsWithImages, outputPath); } if (imageType == ImageType.Primary) { if (item is UserView) { - return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); + return CreateSquareCollage(item, itemsWithImages, outputPath); } if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre || item is PhotoAlbum) { - return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); + return CreateSquareCollage(item, itemsWithImages, outputPath); } - return await CreatePosterCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); + return CreatePosterCollage(item, itemsWithImages, outputPath); } throw new ArgumentException("Unexpected image type"); @@ -346,7 +319,7 @@ namespace Emby.Server.Implementations.Images } } - protected async Task<string> CreateSingleImage(List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType) + protected string CreateSingleImage(List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType) { var image = itemsWithImages .Where(i => i.HasImage(imageType) && i.GetImageInfo(imageType, 0).IsLocalFile && Path.HasExtension(i.GetImagePath(imageType))) diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 64f025d93..54f9ca392 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 3c94f9784..42eda00b7 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -40,7 +40,8 @@ using MediaBrowser.Model.Net; using SortOrder = MediaBrowser.Model.Entities.SortOrder; using VideoResolver = MediaBrowser.Naming.Video.VideoResolver; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.IO; + +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Tasks; @@ -313,7 +314,8 @@ namespace Emby.Server.Implementations.Library { IncludeItemTypes = new[] { typeof(Season).Name }, Recursive = true, - IndexNumber = 0 + IndexNumber = 0, + DtoOptions = new DtoOptions(true) }).Cast<Season>() .Where(i => !string.Equals(i.Name, newName, StringComparison.Ordinal)) @@ -342,7 +344,7 @@ namespace Emby.Server.Implementations.Library } if (item is IItemByName) { - if (!(item is MusicArtist) && !(item is Studio)) + if (!(item is MusicArtist)) { return; } @@ -862,13 +864,19 @@ namespace Emby.Server.Implementations.Library // 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 + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException("path"); + } + var query = new InternalItemsQuery { Path = path, IsFolder = isFolder, SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, - Limit = 1 + Limit = 1, + DtoOptions = new DtoOptions(true) }; return GetItemList(query) @@ -882,7 +890,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task{Person}.</returns> public Person GetPerson(string name) { - return CreateItemByName<Person>(Person.GetPath, name); + return CreateItemByName<Person>(Person.GetPath, name, new DtoOptions(true)); } /// <summary> @@ -892,7 +900,27 @@ namespace Emby.Server.Implementations.Library /// <returns>Task{Studio}.</returns> public Studio GetStudio(string name) { - return CreateItemByName<Studio>(Studio.GetPath, name); + return CreateItemByName<Studio>(Studio.GetPath, name, new DtoOptions(true)); + } + + public Guid GetStudioId(string name) + { + return GetItemByNameId<Studio>(Studio.GetPath, name); + } + + public Guid GetGenreId(string name) + { + return GetItemByNameId<Genre>(Genre.GetPath, name); + } + + public Guid GetMusicGenreId(string name) + { + return GetItemByNameId<MusicGenre>(MusicGenre.GetPath, name); + } + + public Guid GetGameGenreId(string name) + { + return GetItemByNameId<GameGenre>(GameGenre.GetPath, name); } /// <summary> @@ -902,7 +930,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task{Genre}.</returns> public Genre GetGenre(string name) { - return CreateItemByName<Genre>(Genre.GetPath, name); + return CreateItemByName<Genre>(Genre.GetPath, name, new DtoOptions(true)); } /// <summary> @@ -912,7 +940,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task{MusicGenre}.</returns> public MusicGenre GetMusicGenre(string name) { - return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name); + return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name, new DtoOptions(true)); } /// <summary> @@ -922,7 +950,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task{GameGenre}.</returns> public GameGenre GetGameGenre(string name) { - return CreateItemByName<GameGenre>(GameGenre.GetPath, name); + return CreateItemByName<GameGenre>(GameGenre.GetPath, name, new DtoOptions(true)); } /// <summary> @@ -940,7 +968,7 @@ namespace Emby.Server.Implementations.Library var name = value.ToString(CultureInfo.InvariantCulture); - return CreateItemByName<Year>(Year.GetPath, name); + return CreateItemByName<Year>(Year.GetPath, name, new DtoOptions(true)); } /// <summary> @@ -950,10 +978,15 @@ namespace Emby.Server.Implementations.Library /// <returns>Task{Genre}.</returns> public MusicArtist GetArtist(string name) { - return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name); + return GetArtist(name, new DtoOptions(true)); + } + + public MusicArtist GetArtist(string name, DtoOptions options) + { + return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name, options); } - private T CreateItemByName<T>(Func<string, string> getPathFn, string name) + private T CreateItemByName<T>(Func<string, string> getPathFn, string name, DtoOptions options) where T : BaseItem, new() { if (typeof(T) == typeof(MusicArtist)) @@ -961,7 +994,8 @@ namespace Emby.Server.Implementations.Library var existing = GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(T).Name }, - Name = name + Name = name, + DtoOptions = options }).Cast<MusicArtist>() .OrderBy(i => i.IsAccessedByName ? 1 : 0) @@ -974,14 +1008,13 @@ namespace Emby.Server.Implementations.Library } } - var path = getPathFn(name); - var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds; - var id = GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId); + var id = GetItemByNameId<T>(getPathFn, name); var item = GetItemById(id) as T; if (item == null) { + var path = getPathFn(name); item = new T { Name = name, @@ -998,52 +1031,12 @@ namespace Emby.Server.Implementations.Library return item; } - public IEnumerable<MusicArtist> GetAlbumArtists(IEnumerable<IHasAlbumArtist> items) - { - var names = items - .SelectMany(i => i.AlbumArtists) - .DistinctNames() - .Select(i => - { - try - { - var artist = GetArtist(i); - - return artist; - } - catch - { - // Already logged at lower levels - return null; - } - }) - .Where(i => i != null); - - return names; - } - - public IEnumerable<MusicArtist> GetArtists(IEnumerable<IHasArtist> items) + private Guid GetItemByNameId<T>(Func<string, string> getPathFn, string name) + where T : BaseItem, new() { - var names = items - .SelectMany(i => i.AllArtists) - .DistinctNames() - .Select(i => - { - try - { - var artist = GetArtist(i); - - return artist; - } - catch - { - // Already logged at lower levels - return null; - } - }) - .Where(i => i != null); - - return names; + var path = getPathFn(name); + var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds; + return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId); } /// <summary> @@ -1098,12 +1091,6 @@ namespace Emby.Server.Implementations.Library try { await PerformLibraryValidation(progress, cancellationToken).ConfigureAwait(false); - - if (!ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey) - { - ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey = true; - ConfigurationManager.SaveConfiguration(); - } } finally { @@ -1558,7 +1545,7 @@ namespace Emby.Server.Implementations.Library } } - query.ParentId = null; + query.Parent = null; } private void AddUserToQuery(InternalItemsQuery query, User user) diff --git a/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs b/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs index 7424ed5e5..e64980dff 100644 --- a/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs +++ b/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -27,7 +28,8 @@ namespace Emby.Server.Implementations.Library var items = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(BoxSet).Name, typeof(Game).Name, typeof(Movie).Name, typeof(Series).Name }, - Recursive = true + Recursive = true, + DtoOptions = new DtoOptions(true) }).OfType<IHasTrailers>().ToList(); @@ -40,7 +42,8 @@ namespace Emby.Server.Implementations.Library { IncludeItemTypes = new[] { typeof(Trailer).Name }, TrailerTypes = trailerTypes, - Recursive = true + Recursive = true, + DtoOptions = new DtoOptions(false) }).ToArray(); diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index b15c01125..f0d07cc3c 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Playlists; using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Controller.Dto; using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Library @@ -18,47 +19,48 @@ namespace Emby.Server.Implementations.Library _libraryManager = libraryManager; } - public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user) + public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions) { var list = new List<Audio> { item }; - return list.Concat(GetInstantMixFromGenres(item.Genres, user)); + return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)); } - public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist item, User user) + public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions) { - return GetInstantMixFromGenres(item.Genres, user); + return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user) + public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions) { - return GetInstantMixFromGenres(item.Genres, user); + return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user) + public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user, DtoOptions dtoOptions) { var genres = item .GetRecursiveChildren(user, new InternalItemsQuery(user) { - IncludeItemTypes = new[] { typeof(Audio).Name } + IncludeItemTypes = new[] { typeof(Audio).Name }, + DtoOptions = dtoOptions }) .Cast<Audio>() .SelectMany(i => i.Genres) .Concat(item.Genres) .DistinctNames(); - return GetInstantMixFromGenres(genres, user); + return GetInstantMixFromGenres(genres, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user) + public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user, DtoOptions dtoOptions) { - return GetInstantMixFromGenres(item.Genres, user); + return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user) + public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions) { var genreIds = genres.DistinctNames().Select(i => { @@ -73,10 +75,10 @@ namespace Emby.Server.Implementations.Library }).Where(i => i != null); - return GetInstantMixFromGenreIds(genreIds, user); + return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user) + public IEnumerable<Audio> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user, DtoOptions dtoOptions) { return _libraryManager.GetItemList(new InternalItemsQuery(user) { @@ -86,47 +88,49 @@ namespace Emby.Server.Implementations.Library Limit = 200, - SortBy = new[] { ItemSortBy.Random } + SortBy = new[] { ItemSortBy.Random }, + + DtoOptions = dtoOptions }).Cast<Audio>(); } - public IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user) + public IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) { var genre = item as MusicGenre; if (genre != null) { - return GetInstantMixFromGenreIds(new[] { item.Id.ToString("N") }, user); + return GetInstantMixFromGenreIds(new[] { item.Id.ToString("N") }, user, dtoOptions); } var playlist = item as Playlist; if (playlist != null) { - return GetInstantMixFromPlaylist(playlist, user); + return GetInstantMixFromPlaylist(playlist, user, dtoOptions); } var album = item as MusicAlbum; if (album != null) { - return GetInstantMixFromAlbum(album, user); + return GetInstantMixFromAlbum(album, user, dtoOptions); } var artist = item as MusicArtist; if (artist != null) { - return GetInstantMixFromArtist(artist, user); + return GetInstantMixFromArtist(artist, user, dtoOptions); } var song = item as Audio; if (song != null) { - return GetInstantMixFromSong(song, user); + return GetInstantMixFromSong(song, user, dtoOptions); } var folder = item as Folder; if (folder != null) { - return GetInstantMixFromFolder(folder, user); + return GetInstantMixFromFolder(folder, user, dtoOptions); } return new Audio[] { }; diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 1d3cacc1d..24dc1104a 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -5,8 +5,6 @@ using System; using System.IO; using System.Linq; using System.Text.RegularExpressions; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index b3d6d4ad7..806e20f00 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -8,7 +8,7 @@ using MediaBrowser.Naming.Audio; using System; using System.Collections.Generic; using System.IO; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 2971405b9..2ad839673 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Logging; using System; using System.IO; using System.Linq; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 3550a83b9..69563e5a0 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -11,7 +11,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 1bec1073d..6c7c1f052 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -5,7 +5,7 @@ using MediaBrowser.Controller.Resolvers; using System; using System.IO; using System.Linq; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index e5cad9f91..60260e98a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -10,7 +10,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.IO; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 5f88a6c66..6f63322c8 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Extensions; namespace Emby.Server.Implementations.Library @@ -175,7 +176,17 @@ namespace Emby.Server.Implementations.Library IsNews = query.IsNews, IsSeries = query.IsSeries, IsSports = query.IsSports, - MediaTypes = query.MediaTypes + MediaTypes = query.MediaTypes, + + DtoOptions = new DtoOptions + { + Fields = new List<ItemFields> + { + ItemFields.AirTime, + ItemFields.DateCreated, + ItemFields.ChannelInfo + } + } }); // Add search hints based on item name diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 5a14edf13..e066ab61b 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -182,21 +182,21 @@ namespace Emby.Server.Implementations.Library return GetUserData(userId, item.Id, item.GetUserDataKeys()); } - public async Task<UserItemDataDto> GetUserDataDto(IHasUserData item, User user) + public UserItemDataDto GetUserDataDto(IHasUserData item, User user) { var userData = GetUserData(user.Id, item); var dto = GetUserItemDataDto(userData); - await item.FillUserDataDtoValues(dto, userData, null, user, new List<ItemFields>()).ConfigureAwait(false); + item.FillUserDataDtoValues(dto, userData, null, user, new List<ItemFields>()); return dto; } - public async Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, List<ItemFields> fields) + public UserItemDataDto GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, List<ItemFields> fields) { var userData = GetUserData(user.Id, item); var dto = GetUserItemDataDto(userData); - await item.FillUserDataDtoValues(dto, userData, itemDto, user, fields).ConfigureAwait(false); + item.FillUserDataDtoValues(dto, userData, itemDto, user, fields); return dto; } diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 9e1291847..f403ca266 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Globalization; @@ -190,11 +191,11 @@ namespace Emby.Server.Implementations.Library return _libraryManager.GetShadowView(parent, viewType, sortName, cancellationToken); } - public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request) + public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request, DtoOptions options) { var user = _userManager.GetUserById(request.UserId); - var libraryItems = GetItemsForLatestItems(user, request); + var libraryItems = GetItemsForLatestItems(user, request, options); var list = new List<Tuple<BaseItem, List<BaseItem>>>(); @@ -230,7 +231,7 @@ namespace Emby.Server.Implementations.Library return list; } - private IEnumerable<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request) + private IEnumerable<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options) { var parentId = request.ParentId; @@ -289,7 +290,8 @@ namespace Emby.Server.Implementations.Library IsVirtualItem = false, Limit = limit * 5, SourceTypes = parents.Count == 0 ? new[] { SourceType.Library } : new SourceType[] { }, - IsPlayed = isPlayed + IsPlayed = isPlayed, + DtoOptions = options }, parents); } diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index 7ebfd71c0..ef3b86abf 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -11,7 +11,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index ef440899c..dcfaaa9d7 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -3,9 +3,10 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; -using MediaBrowser.Common.IO; + using MediaBrowser.Common.Net; using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Logging; @@ -29,7 +30,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return targetFile; } - public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + { + if (directStreamProvider != null) + { + return RecordFromDirectStreamProvider(directStreamProvider, targetFile, duration, onStarted, cancellationToken); + } + + return RecordFromMediaSource(mediaSource, targetFile, duration, onStarted, cancellationToken); + } + + private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + { + using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + { + onStarted(); + + _logger.Info("Copying recording stream to file {0}", targetFile); + + // The media source if infinite so we need to handle stopping ourselves + var durationToken = new CancellationTokenSource(duration); + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + + await directStreamProvider.CopyToAsync(output, cancellationToken).ConfigureAwait(false); + } + + _logger.Info("Recording completed to file {0}", targetFile); + } + + private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { var httpRequestOptions = new HttpRequestOptions { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index f1b3f41b4..6ce2b88f1 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -28,8 +28,9 @@ using System.Xml; using MediaBrowser.Model.IO; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; @@ -1232,7 +1233,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV RequiresClosing = false, Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http, BufferMs = 0, - IgnoreDts = true + IgnoreDts = true, + IgnoreIndex = true }; var isAudio = false; @@ -1517,7 +1519,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV EnforceKeepUpTo(timer, seriesPath); }; - await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false); + await recorder.Record(liveStreamInfo.Item1 as IDirectStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false); recordingStatus = RecordingStatus.Completed; _logger.Info("Recording completed: {0}", recordPath); @@ -1634,15 +1636,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - var episodesToDelete = (await librarySeries.GetItems(new InternalItemsQuery + var episodesToDelete = (librarySeries.GetItems(new InternalItemsQuery { SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, IsVirtualItem = false, IsFolder = false, - Recursive = true + Recursive = true, + DtoOptions = new DtoOptions(true) - }).ConfigureAwait(false)) + })) .Items .Where(i => i.LocationType == LocationType.FileSystem && _fileSystem.FileExists(i.Path)) .Skip(seriesTimer.KeepUpTo - 1) @@ -1759,20 +1762,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var config = GetConfiguration(); - if (config.EnableRecordingEncoding) - { - var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false); + var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false); - if (regInfo.IsValid) + if (regInfo.IsValid) + { + if (config.EnableRecordingEncoding) { return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient, _processFactory, _config); } + + return new DirectRecorder(_logger, _httpClient, _fileSystem); + + //var options = new LiveTvOptions + //{ + // EnableOriginalAudioWithEncodedRecordings = true, + // RecordedVideoCodec = "copy", + // RecordingEncodingFormat = "ts" + //}; + //return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, options, _httpClient, _processFactory, _config); } - return new DirectRecorder(_logger, _httpClient, _fileSystem); + throw new InvalidOperationException("Emby DVR Requires an active Emby Premiere subscription."); } - private async void OnSuccessfulRecording(TimerInfo timer, string path) + private void OnSuccessfulRecording(TimerInfo timer, string path) { //if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize) //{ @@ -1967,7 +1980,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, Limit = 1, - ExternalId = timer.ProgramId + ExternalId = timer.ProgramId, + DtoOptions = new DtoOptions(true) }).FirstOrDefault() as LiveTvProgram; @@ -2501,16 +2515,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) { - var result = _libraryManager.GetItemsResult(new InternalItemsQuery + var result = _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Episode).Name }, ParentIndexNumber = program.SeasonNumber.Value, IndexNumber = program.EpisodeNumber.Value, AncestorIds = seriesIds, - IsVirtualItem = false + IsVirtualItem = false, + Limit = 1 }); - if (result.TotalRecordCount > 0) + if (result.Count > 0) { return true; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 790e6c27d..6173068cc 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -8,7 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; -using MediaBrowser.Common.IO; + using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -21,6 +21,7 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Library; namespace Emby.Server.Implementations.LiveTv.EmbyTV { @@ -64,6 +65,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { return "mkv"; } + if (string.Equals(format, "ts", StringComparison.OrdinalIgnoreCase)) + { + return "ts"; + } return "mp4"; } @@ -90,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return Path.ChangeExtension(targetFile, "." + extension); } - public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { //var durationToken = new CancellationTokenSource(duration); //cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; @@ -177,6 +182,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV videoArgs = "-codec:v:0 copy"; } + videoArgs += " -fflags +genpts"; + var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); var flags = new List<string>(); @@ -188,28 +195,37 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { flags.Add("+ignidx"); } + if (mediaSource.GenPtsInput) + { + flags.Add("+genpts"); + } - var inputModifiers = "-async 1 -vsync -1"; + var inputModifier = "-async 1 -vsync -1"; if (flags.Count > 0) { - inputModifiers += " -fflags " + string.Join("", flags.ToArray()); + inputModifier += " -fflags " + string.Join("", flags.ToArray()); } if (!string.IsNullOrWhiteSpace(GetEncodingOptions().HardwareAccelerationType)) { - inputModifiers += " -hwaccel auto"; + inputModifier += " -hwaccel auto"; } if (mediaSource.ReadAtNativeFramerate) { - inputModifiers += " -re"; + inputModifier += " -re"; + } + + if (mediaSource.RequiresLooping) + { + inputModifier += " -stream_loop -1"; } var analyzeDurationSeconds = 5; var analyzeDuration = " -analyzeduration " + (analyzeDurationSeconds * 1000000).ToString(CultureInfo.InvariantCulture); - inputModifiers += analyzeDuration; + inputModifier += analyzeDuration; var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn"; @@ -228,7 +244,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV durationParam, outputParam); - return inputModifiers + " " + commandLineArgs; + return inputModifier + " " + commandLineArgs; } private string GetAudioArgs(MediaSourceInfo mediaSource) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs index 3b5e60c4a..e639a312c 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; namespace Emby.Server.Implementations.LiveTv.EmbyTV @@ -10,13 +11,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV /// <summary> /// Records the specified media source. /// </summary> - /// <param name="mediaSource">The media source.</param> - /// <param name="targetFile">The target file.</param> - /// <param name="duration">The duration.</param> - /// <param name="onStarted">The on started.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken); + Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken); string GetOutputPath(MediaSourceInfo mediaSource, string targetFile); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 953cb8e41..4ba2269a6 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 17de93a3c..94be4a02e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } else { - name += " " + info.StartDate.ToString("yyyy-MM-dd") + " " + info.Id; + name += " " + info.StartDate.ToString("yyyy-MM-dd"); } return name; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs index 7bf6bf1ca..843ba7e42 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs @@ -2,8 +2,6 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.LiveTv.EmbyTV diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 2eec3df8a..380b24800 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -8,7 +8,7 @@ using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Threading; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index a36cb124d..da6759b34 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -167,10 +167,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var programEntry = programDict[schedule.programID]; - var data = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>(); - data = data.OrderByDescending(GetSizeOrder).ToList(); + var allImages = (images[imageIndex].data ?? new List<ScheduleDirect.ImageData>()).OrderByDescending(GetSizeOrder).ToList(); + var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)).ToList(); + + programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, "Logo", true, 600) ?? + GetProgramImage(ApiUrl, allImages, "Logo", true, 600); - programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 600); //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false); //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 43b055098..5c5072192 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -149,7 +149,11 @@ namespace Emby.Server.Implementations.LiveTv IncludeItemTypes = new string[] { typeof(Series).Name }, Name = seriesName, Limit = 1, - ImageTypes = new ImageType[] { ImageType.Thumb } + ImageTypes = new ImageType[] { ImageType.Thumb }, + DtoOptions = new DtoOptions + { + Fields = new List<MediaBrowser.Model.Querying.ItemFields>() + } }).FirstOrDefault(); @@ -191,7 +195,11 @@ namespace Emby.Server.Implementations.LiveTv IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, ExternalSeriesId = programSeriesId, Limit = 1, - ImageTypes = new ImageType[] { ImageType.Primary } + ImageTypes = new ImageType[] { ImageType.Primary }, + DtoOptions = new DtoOptions + { + Fields = new List<MediaBrowser.Model.Querying.ItemFields>() + } }).FirstOrDefault(); @@ -239,7 +247,11 @@ namespace Emby.Server.Implementations.LiveTv IncludeItemTypes = new string[] { typeof(Series).Name }, Name = seriesName, Limit = 1, - ImageTypes = new ImageType[] { ImageType.Thumb } + ImageTypes = new ImageType[] { ImageType.Thumb }, + DtoOptions = new DtoOptions + { + Fields = new List<MediaBrowser.Model.Querying.ItemFields>() + } }).FirstOrDefault(); @@ -281,14 +293,22 @@ namespace Emby.Server.Implementations.LiveTv IncludeItemTypes = new string[] { typeof(Series).Name }, Name = seriesName, Limit = 1, - ImageTypes = new ImageType[] { ImageType.Primary } + ImageTypes = new ImageType[] { ImageType.Primary }, + DtoOptions = new DtoOptions + { + Fields = new List<MediaBrowser.Model.Querying.ItemFields>() + } }).FirstOrDefault() ?? _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, ExternalSeriesId = programSeriesId, Limit = 1, - ImageTypes = new ImageType[] { ImageType.Primary } + ImageTypes = new ImageType[] { ImageType.Primary }, + DtoOptions = new DtoOptions + { + Fields = new List<MediaBrowser.Model.Querying.ItemFields>() + } }).FirstOrDefault(); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index fa86ac36d..c2f057560 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -26,7 +26,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Common.Events; -using MediaBrowser.Common.IO; + using MediaBrowser.Common.Security; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.LiveTv } } - public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken) + public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); @@ -192,7 +192,8 @@ namespace Emby.Server.Implementations.LiveTv IsFavorite = query.IsFavorite, IsLiked = query.IsLiked, StartIndex = query.StartIndex, - Limit = query.Limit + Limit = query.Limit, + DtoOptions = dtoOptions }; internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending))); @@ -249,7 +250,7 @@ namespace Emby.Server.Implementations.LiveTv { Id = id - }, cancellationToken).ConfigureAwait(false); + }, new DtoOptions(), cancellationToken).ConfigureAwait(false); return result.Items.FirstOrDefault(); } @@ -477,7 +478,8 @@ namespace Emby.Server.Implementations.LiveTv if (!(service is EmbyTV.EmbyTV)) { - // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says + // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says + //mediaSource.SupportsDirectPlay = false; mediaSource.SupportsDirectStream = false; mediaSource.SupportsTranscoding = true; foreach (var stream in mediaSource.MediaStreams) @@ -864,13 +866,6 @@ namespace Emby.Server.Implementations.LiveTv return item.Id; } - - - private string GetExternalSeriesIdLegacy(BaseItem item) - { - return item.GetProviderId("ProviderExternalSeriesId"); - } - public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null) { var program = GetInternalProgram(id); @@ -881,11 +876,6 @@ namespace Emby.Server.Implementations.LiveTv var externalSeriesId = program.ExternalSeriesId; - if (string.IsNullOrWhiteSpace(externalSeriesId)) - { - externalSeriesId = GetExternalSeriesIdLegacy(program); - } - list.Add(new Tuple<BaseItemDto, string, string, string>(dto, program.ServiceName, GetItemExternalId(program), externalSeriesId)); await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false); @@ -905,6 +895,8 @@ namespace Emby.Server.Implementations.LiveTv query.SortBy = new[] { ItemSortBy.StartDate }; } + RemoveFields(options); + var internalQuery = new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, @@ -964,8 +956,6 @@ namespace Emby.Server.Implementations.LiveTv var queryResult = _libraryManager.QueryItems(internalQuery); - RemoveFields(options); - var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray(); var result = new QueryResult<BaseItemDto> @@ -1044,12 +1034,12 @@ namespace Emby.Server.Implementations.LiveTv public async Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken) { + RemoveFields(options); + var internalResult = await GetRecommendedProgramsInternal(query, options, cancellationToken).ConfigureAwait(false); var user = _userManager.GetUserById(query.UserId); - RemoveFields(options); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); var result = new QueryResult<BaseItemDto> @@ -1332,7 +1322,8 @@ namespace Emby.Server.Implementations.LiveTv { IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, - ChannelIds = new string[] { currentChannel.Id.ToString("N") } + ChannelIds = new string[] { currentChannel.Id.ToString("N") }, + DtoOptions = new DtoOptions(true) }).Cast<LiveTvProgram>().ToDictionary(i => i.Id); @@ -1435,7 +1426,8 @@ namespace Emby.Server.Implementations.LiveTv { var list = _itemRepo.GetItemIdsList(new InternalItemsQuery { - IncludeItemTypes = validTypes + IncludeItemTypes = validTypes, + DtoOptions = new DtoOptions(false) }).ToList(); @@ -1662,6 +1654,8 @@ namespace Emby.Server.Implementations.LiveTv includeItemTypes.Add(typeof(Series).Name); + RemoveFields(options); + var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) { Recursive = true, @@ -1671,11 +1665,10 @@ namespace Emby.Server.Implementations.LiveTv SortOrder = SortOrder.Descending, EnableTotalRecordCount = query.EnableTotalRecordCount, IncludeItemTypes = includeItemTypes.ToArray(), - ExcludeItemTypes = excludeItemTypes.ToArray() + ExcludeItemTypes = excludeItemTypes.ToArray(), + DtoOptions = options }); - RemoveFields(options); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); return new QueryResult<BaseItemDto> @@ -1685,7 +1678,7 @@ namespace Emby.Server.Implementations.LiveTv }; } - public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken) + public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); if (user != null && !IsLiveTvEnabled(user)) @@ -1695,14 +1688,15 @@ namespace Emby.Server.Implementations.LiveTv if (_services.Count == 1 && !(query.IsInProgress ?? false) && (!query.IsLibraryItem.HasValue || query.IsLibraryItem.Value)) { - return GetEmbyRecordings(query, new DtoOptions(), user); + return GetEmbyRecordings(query, options, user); } await RefreshRecordings(cancellationToken).ConfigureAwait(false); var internalQuery = new InternalItemsQuery(user) { - IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name } + IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, + DtoOptions = options }; if (!string.IsNullOrEmpty(query.ChannelId)) @@ -1871,11 +1865,6 @@ namespace Emby.Server.Implementations.LiveTv var externalSeriesId = program.ExternalSeriesId; - if (string.IsNullOrWhiteSpace(externalSeriesId)) - { - externalSeriesId = GetExternalSeriesIdLegacy(program); - } - programTuples.Add(new Tuple<BaseItemDto, string, string, string>(dto, serviceName, GetItemExternalId(program), externalSeriesId)); } @@ -1952,10 +1941,10 @@ namespace Emby.Server.Implementations.LiveTv { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); - var internalResult = await GetInternalRecordings(query, cancellationToken).ConfigureAwait(false); - RemoveFields(options); + var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false); + var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); return new QueryResult<BaseItemDto> @@ -2298,7 +2287,8 @@ namespace Emby.Server.Implementations.LiveTv MinEndDate = now, Limit = channelIds.Length, SortBy = new[] { "StartDate" }, - TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") } + TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }, + DtoOptions = options }).ToList() : new List<BaseItem>(); @@ -2600,7 +2590,7 @@ namespace Emby.Server.Implementations.LiveTv { UserId = query.UserId - }, cancellationToken).ConfigureAwait(false); + }, new DtoOptions(), cancellationToken).ConfigureAwait(false); var recordings = recordingResult.Items.OfType<ILiveTvRecording>().ToList(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index d724a9fbc..504f9a6ee 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -421,7 +421,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun SupportsDirectStream = true, SupportsTranscoding = true, IsInfiniteStream = true, - IgnoreDts = true + IgnoreDts = true, + //IgnoreIndex = true, + ReadAtNativeFramerate = true }; mediaSource.InferTotalBitrate(); @@ -505,12 +507,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner) { - return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); + return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment); } // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet - var enableHttpStream = _environment.OperatingSystem == OperatingSystem.OSX || - _environment.OperatingSystem == OperatingSystem.BSD; + var enableHttpStream = _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX + || _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.BSD; enableHttpStream = true; if (enableHttpStream) { @@ -519,17 +521,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var httpUrl = GetApiUrl(info, true) + "/auto/v" + hdhrId; // If raw was used, the tuner doesn't support params - if (!string.IsNullOrWhiteSpace(profile) - && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) { httpUrl += "?transcode=" + profile; } mediaSource.Path = httpUrl; - return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment); } - return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); + return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment); } public async Task Validate(TunerHostInfo info) @@ -596,10 +597,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // Need a way to set the Receive timeout on the socket otherwise this might never timeout? try { - await udpClient.SendAsync(discBytes, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken); + await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken); + var receiveBuffer = new byte[8192]; + while (!cancellationToken.IsCancellationRequested) { - var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + var response = await udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); var deviceIp = response.RemoteEndPoint.IpAddress.Address; // check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs index 2798805fa..5db842dec 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs @@ -2,6 +2,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations.IO; using MediaBrowser.Model.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.System; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -17,24 +19,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { private readonly ILogger _logger; private readonly IHttpClient _httpClient; - private readonly IFileSystem _fileSystem; - private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationHost _appHost; private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); - private readonly MulticastStream _multicastStream; - public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost) - : base(mediaSource) + private readonly string _tempFilePath; + + public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment) + : base(mediaSource, environment, fileSystem) { - _fileSystem = fileSystem; _httpClient = httpClient; _logger = logger; - _appPaths = appPaths; _appHost = appHost; OriginalStreamId = originalStreamId; - _multicastStream = new MulticastStream(_logger); + + _tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts"); } protected override async Task OpenInternal(CancellationToken openCancellationToken) @@ -57,9 +57,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; OpenedMediaSource.Protocol = MediaProtocol.Http; - OpenedMediaSource.SupportsDirectPlay = false; - OpenedMediaSource.SupportsDirectStream = true; - OpenedMediaSource.SupportsTranscoding = true; + //OpenedMediaSource.SupportsDirectPlay = false; + //OpenedMediaSource.SupportsDirectStream = true; + //OpenedMediaSource.SupportsTranscoding = true; await taskCompletionSource.Task.ConfigureAwait(false); @@ -74,9 +74,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return _liveStreamTaskCompletionSource.Task; } - private async Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) + private Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) { - await Task.Run(async () => + return Task.Run(async () => { var isFirstAttempt = true; @@ -101,13 +101,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { _logger.Info("Beginning multicastStream.CopyUntilCancelled"); - Action onStarted = null; - if (isFirstAttempt) + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); + using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous)) { - onStarted = () => openTaskCompletionSource.TrySetResult(true); - } + ResolveAfterDelay(3000, openTaskCompletionSource); - await _multicastStream.CopyUntilCancelled(response.Content, onStarted, cancellationToken).ConfigureAwait(false); + //await response.Content.CopyToAsync(fileStream, 81920, cancellationToken).ConfigureAwait(false); + await AsyncStreamCopier.CopyStream(response.Content, fileStream, 81920, 4, cancellationToken).ConfigureAwait(false); + } } } } @@ -131,13 +132,60 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } _liveStreamTaskCompletionSource.TrySetResult(true); + await DeleteTempFile(_tempFilePath).ConfigureAwait(false); + }); + } - }).ConfigureAwait(false); + private void ResolveAfterDelay(int delayMs, TaskCompletionSource<bool> openTaskCompletionSource) + { + Task.Run(async () => + { + await Task.Delay(delayMs).ConfigureAwait(false); + openTaskCompletionSource.TrySetResult(true); + }); } public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - return _multicastStream.CopyToAsync(stream); + return CopyFileTo(_tempFilePath, false, stream, cancellationToken); + } + + protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken) + { + var eofCount = 0; + + long startPosition = -25000; + if (startPosition < 0) + { + var length = FileSystem.GetFileInfo(path).Length; + startPosition = Math.Max(length - startPosition, 0); + } + + using (var inputStream = GetInputStream(path, startPosition, true)) + { + if (startPosition > 0) + { + inputStream.Position = startPosition; + } + + while (eofCount < 20 || !allowEndOfFile) + { + var bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 4, cancellationToken).ConfigureAwait(false); + + //var position = fs.Position; + //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); + + if (bytesRead == 0) + { + eofCount++; + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; + } + } + } } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 2c678d9f8..41b058baf 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -46,10 +46,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public class HdHomerunChannelCommands : IHdHomerunChannelCommands { private string _channel; + private string _profile; - public HdHomerunChannelCommands(string channel) + public HdHomerunChannelCommands(string channel, string profile) { _channel = channel; + _profile = profile; } public IEnumerable<Tuple<string, string>> GetCommands() @@ -57,7 +59,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var commands = new List<Tuple<string, string>>(); if (!String.IsNullOrEmpty(_channel)) - commands.Add(Tuple.Create("vchannel", _channel)); + { + if (!string.IsNullOrWhiteSpace(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase)) + { + commands.Add(Tuple.Create("vchannel", String.Format("{0} transcode={1}", _channel, _profile))); + } + else + { + commands.Add(Tuple.Create("vchannel", _channel)); + } + } return commands; } @@ -103,8 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var ipEndPoint = new IpEndPointInfo(remoteIp, HdHomeRunPort); var lockkeyMsg = CreateGetMessage(tuner, "lockkey"); - await socket.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken); - var response = await socket.ReceiveAsync(cancellationToken).ConfigureAwait(false); + await socket.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken); + + var receiveBuffer = new byte[8192]; + var response = await socket.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); + string returnVal; ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal); @@ -117,6 +131,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort)) { + var receiveBuffer = new byte[8192]; + if (!_lockkey.HasValue) { var rand = new Random(); @@ -133,8 +149,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _activeTuner = i; var lockKeyString = String.Format("{0:d}", _lockkey.Value); var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); - await tcpClient.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); - var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + await tcpClient.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); + var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); string returnVal; // parse response to make sure it worked if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) @@ -144,8 +160,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun foreach(Tuple<string,string> command in commandList) { var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, _lockkey.Value); - await tcpClient.SendAsync(channelMsg, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); - response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); + response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) { @@ -158,8 +174,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort); var targetMsg = CreateSetMessage(i, "target", targetValue, _lockkey.Value); - await tcpClient.SendAsync(targetMsg, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); - response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + await tcpClient.SendToAsync(targetMsg, 0, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); + response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) { @@ -180,11 +196,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort)) { var commandList = commands.GetCommands(); + var receiveBuffer = new byte[8192]; + foreach (Tuple<string, string> command in commandList) { var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey.Value); - await tcpClient.SendAsync(channelMsg, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false); - var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false); + var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked string returnVal; if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) @@ -209,12 +227,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private async Task ReleaseLockkey(ISocket tcpClient) { var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", _lockkey); - await tcpClient.SendAsync(releaseTarget, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false); - await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false); + await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false); + + var receiveBuffer = new byte[8192]; + + await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false); var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", _lockkey); _lockkey = null; - await tcpClient.SendAsync(releaseKeyMsg, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false); - await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false); + await tcpClient.SendToAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false); + await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false); } private static byte[] CreateGetMessage(int tuner, string name) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index e1572ea3f..2989177c0 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; @@ -14,39 +15,35 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider { private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly IFileSystem _fileSystem; - private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationHost _appHost; private readonly ISocketFactory _socketFactory; private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); - private readonly MulticastStream _multicastStream; private readonly IHdHomerunChannelCommands _channelCommands; private readonly int _numTuners; private readonly INetworkManager _networkManager; - public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager) - : base(mediaSource) + private readonly string _tempFilePath; + + public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) + : base(mediaSource, environment, fileSystem) { - _fileSystem = fileSystem; - _httpClient = httpClient; _logger = logger; - _appPaths = appPaths; _appHost = appHost; _socketFactory = socketFactory; _networkManager = networkManager; OriginalStreamId = originalStreamId; - _multicastStream = new MulticastStream(_logger); _channelCommands = channelCommands; _numTuners = numTuners; + _tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts"); } protected override async Task OpenInternal(CancellationToken openCancellationToken) @@ -70,9 +67,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; OpenedMediaSource.Protocol = MediaProtocol.Http; - OpenedMediaSource.SupportsDirectPlay = false; - OpenedMediaSource.SupportsDirectStream = true; - OpenedMediaSource.SupportsTranscoding = true; + //OpenedMediaSource.SupportsDirectPlay = false; + //OpenedMediaSource.SupportsDirectStream = true; + //OpenedMediaSource.SupportsTranscoding = true; await taskCompletionSource.Task.ConfigureAwait(false); @@ -87,9 +84,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return _liveStreamTaskCompletionSource.Task; } - private async Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) + private Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) { - await Task.Run(async () => + return Task.Run(async () => { var isFirstAttempt = true; using (var udpClient = _socketFactory.CreateUdpSocket(localPort)) @@ -124,13 +121,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (!cancellationToken.IsCancellationRequested) { - Action onStarted = null; - if (isFirstAttempt) + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); + using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous)) { - onStarted = () => openTaskCompletionSource.TrySetResult(true); + await CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken).ConfigureAwait(false); } - - await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), onStarted, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException ex) @@ -159,135 +154,109 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - }).ConfigureAwait(false); + await DeleteTempFile(_tempFilePath).ConfigureAwait(false); + }); } - public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) + private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource) { - return _multicastStream.CopyToAsync(stream); + Task.Run(() => + { + openTaskCompletionSource.TrySetResult(true); + }); } - } - - // This handles the ReadAsync function only of a Stream object - // This is used to wrap a UDP socket into a stream for MulticastStream which only uses ReadAsync - public class UdpClientStream : Stream - { - private static int RtpHeaderBytes = 12; - private static int PacketSize = 1316; - private readonly ISocket _udpClient; - bool disposed; - public UdpClientStream(ISocket udpClient) : base() + public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - _udpClient = udpClient; + return CopyFileTo(_tempFilePath, false, stream, cancellationToken); } - public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken) { - if (buffer == null) - throw new ArgumentNullException("buffer"); + var eofCount = 0; - if (offset + count < 0) - throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count"); + long startPosition = -25000; + if (startPosition < 0) + { + var length = FileSystem.GetFileInfo(path).Length; + startPosition = Math.Max(length - startPosition, 0); + } - if (offset + count > buffer.Length) - throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count"); + using (var inputStream = GetInputStream(path, startPosition, true)) + { + if (startPosition > 0) + { + inputStream.Position = startPosition; + } - if (disposed) - throw new ObjectDisposedException(typeof(UdpClientStream).ToString()); + while (eofCount < 20 || !allowEndOfFile) + { + var bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 4, cancellationToken).ConfigureAwait(false); - // This will always receive a 1328 packet size (PacketSize + RtpHeaderSize) - // The RTP header will be stripped so see how many reads we need to make to fill the buffer. - int numReads = count / PacketSize; - int totalBytesRead = 0; + //var position = fs.Position; + //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - for (int i = 0; i < numReads; ++i) - { - var data = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); - - var bytesRead = data.ReceivedBytes - RtpHeaderBytes; - - // remove rtp header - Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead); - offset += bytesRead; - totalBytesRead += bytesRead; + if (bytesRead == 0) + { + eofCount++; + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; + } + } } - return totalBytesRead; } - protected override void Dispose(bool disposing) + private static int RtpHeaderBytes = 12; + private Task CopyTo(ISocket udpClient, Stream outputStream, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) { - disposed = true; + return CopyStream(_socketFactory.CreateNetworkStream(udpClient, false), outputStream, 81920, 4, openTaskCompletionSource, cancellationToken); } - public override bool CanRead + private Task CopyStream(Stream source, Stream target, int bufferSize, int bufferCount, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) { - get - { - throw new NotImplementedException(); - } - } + var copier = new AsyncStreamCopier(source, target, 0, cancellationToken, false, bufferSize, bufferCount); + copier.IndividualReadOffset = RtpHeaderBytes; - public override bool CanSeek - { - get - { - throw new NotImplementedException(); - } - } + var taskCompletion = new TaskCompletionSource<long>(); - public override bool CanWrite - { - get - { - throw new NotImplementedException(); - } - } + copier.TaskCompletionSource = taskCompletion; - public override long Length - { - get - { - throw new NotImplementedException(); - } - } + var result = copier.BeginCopy(StreamCopyCallback, copier); - public override long Position - { - get + if (openTaskCompletionSource != null) { - throw new NotImplementedException(); + Resolve(openTaskCompletionSource); + openTaskCompletionSource = null; } - set + if (result.CompletedSynchronously) { - throw new NotImplementedException(); + StreamCopyCallback(result); } - } - public override void Flush() - { - throw new NotImplementedException(); - } + cancellationToken.Register(() => taskCompletion.TrySetCanceled()); - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); + return taskCompletion.Task; } - public override long Seek(long offset, SeekOrigin origin) + private void StreamCopyCallback(IAsyncResult result) { - throw new NotImplementedException(); - } + var copier = (AsyncStreamCopier)result.AsyncState; + var taskCompletion = copier.TaskCompletionSource; - public override void SetLength(long value) - { - throw new NotImplementedException(); + try + { + copier.EndCopy(result); + taskCompletion.TrySetResult(0); + } + catch (Exception ex) + { + taskCompletion.TrySetException(ex); + } } - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 8cf1106f0..12b7901f9 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -11,7 +11,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -19,6 +19,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.System; namespace Emby.Server.Implementations.LiveTv.TunerHosts { @@ -27,13 +28,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; private readonly IServerApplicationHost _appHost; + private readonly IEnvironmentInfo _environment; - public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost) + public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder) { _fileSystem = fileSystem; _httpClient = httpClient; _appHost = appHost; + _environment = environment; } public override string Type @@ -73,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false); - var liveStream = new LiveStream(sources.First()); + var liveStream = new LiveStream(sources.First(), _environment, _fileSystem); return liveStream; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 8c4b9bf60..4c1190e0e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.IO; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs index 02ebbcf16..e650086d3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs @@ -15,7 +15,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { private readonly ConcurrentDictionary<Guid,QueueStream> _outputStreams = new ConcurrentDictionary<Guid, QueueStream>(); private const int BufferSize = 81920; - private CancellationToken _cancellationToken; private readonly ILogger _logger; public MulticastStream(ILogger logger) @@ -25,8 +24,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken) { - _cancellationToken = cancellationToken; - byte[] buffer = new byte[BufferSize]; if (source == null) @@ -72,59 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - private static int RtpHeaderBytes = 12; - public async Task CopyUntilCancelled(ISocket udpClient, Action onStarted, CancellationToken cancellationToken) - { - _cancellationToken = cancellationToken; - - while (!cancellationToken.IsCancellationRequested) - { - var receiveToken = cancellationToken; - - // On the first connection attempt, put a timeout to avoid being stuck indefinitely in the event of failure - if (onStarted != null) - { - receiveToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token; - } - - var data = await udpClient.ReceiveAsync(receiveToken).ConfigureAwait(false); - var bytesRead = data.ReceivedBytes - RtpHeaderBytes; - - if (bytesRead > 0) - { - var allStreams = _outputStreams.ToList(); - - if (allStreams.Count == 1) - { - await allStreams[0].Value.WriteAsync(data.Buffer, 0, bytesRead).ConfigureAwait(false); - } - else - { - byte[] copy = new byte[bytesRead]; - Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead); - - foreach (var stream in allStreams) - { - stream.Value.Queue(copy, 0, copy.Length); - } - } - - if (onStarted != null) - { - var onStartedCopy = onStarted; - onStarted = null; - Task.Run(onStartedCopy); - } - } - - else - { - await Task.Delay(100).ConfigureAwait(false); - } - } - } - - public Task CopyToAsync(Stream stream) + public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { var result = new QueueStream(stream, _logger) { @@ -133,7 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts _outputStreams.TryAdd(result.Id, result); - result.Start(_cancellationToken); + result.Start(cancellationToken); return result.TaskCompletion.Task; } diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 884a001f0..1d74e8788 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -13,7 +13,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/Migrations/GuideMigration.cs b/Emby.Server.Implementations/Migrations/GuideMigration.cs index 99b2942dc..78fb6c222 100644 --- a/Emby.Server.Implementations/Migrations/GuideMigration.cs +++ b/Emby.Server.Implementations/Migrations/GuideMigration.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Migrations _taskManager = taskManager; } - public async Task Run() + public Task Run() { var name = "GuideRefresh3"; @@ -42,6 +42,8 @@ namespace Emby.Server.Implementations.Migrations _config.Configuration.Migrations = list.ToArray(); _config.SaveConfiguration(); } + + return Task.FromResult(true); } } } diff --git a/Emby.Server.Implementations/Migrations/LibraryScanMigration.cs b/Emby.Server.Implementations/Migrations/LibraryScanMigration.cs index bd185bc9c..9d7f67a4f 100644 --- a/Emby.Server.Implementations/Migrations/LibraryScanMigration.cs +++ b/Emby.Server.Implementations/Migrations/LibraryScanMigration.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Migrations _taskManager = taskManager; } - public async Task Run() + public Task Run() { var name = "LibraryScan6"; @@ -42,6 +42,8 @@ namespace Emby.Server.Implementations.Migrations _config.Configuration.Migrations = list.ToArray(); _config.SaveConfiguration(); } + + return Task.FromResult(true); } } } diff --git a/Emby.Server.Implementations/Migrations/UpdateLevelMigration.cs b/Emby.Server.Implementations/Migrations/UpdateLevelMigration.cs deleted file mode 100644 index c532ea08d..000000000 --- a/Emby.Server.Implementations/Migrations/UpdateLevelMigration.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Updates; - -namespace Emby.Server.Implementations.Migrations -{ - public class UpdateLevelMigration : IVersionMigration - { - private readonly IServerConfigurationManager _config; - private readonly IServerApplicationHost _appHost; - private readonly IHttpClient _httpClient; - private readonly IJsonSerializer _jsonSerializer; - private readonly string _releaseAssetFilename; - private readonly ILogger _logger; - - public UpdateLevelMigration(IServerConfigurationManager config, IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, string releaseAssetFilename, ILogger logger) - { - _config = config; - _appHost = appHost; - _httpClient = httpClient; - _jsonSerializer = jsonSerializer; - _releaseAssetFilename = releaseAssetFilename; - _logger = logger; - } - - public async Task Run() - { - var lastVersion = _config.Configuration.LastVersion; - var currentVersion = _appHost.ApplicationVersion; - - if (string.Equals(lastVersion, currentVersion.ToString(), StringComparison.OrdinalIgnoreCase)) - { - return; - } - - try - { - var updateLevel = _config.Configuration.SystemUpdateLevel; - - await CheckVersion(currentVersion, updateLevel, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in update migration", ex); - } - } - - private async Task CheckVersion(Version currentVersion, PackageVersionClass currentUpdateLevel, CancellationToken cancellationToken) - { - var releases = await new GithubUpdater(_httpClient, _jsonSerializer) - .GetLatestReleases("MediaBrowser", "Emby", _releaseAssetFilename, cancellationToken).ConfigureAwait(false); - - var newUpdateLevel = GetNewUpdateLevel(currentVersion, currentUpdateLevel, releases); - - if (newUpdateLevel != currentUpdateLevel) - { - _config.Configuration.SystemUpdateLevel = newUpdateLevel; - _config.SaveConfiguration(); - } - } - - private PackageVersionClass GetNewUpdateLevel(Version currentVersion, PackageVersionClass currentUpdateLevel, List<GithubUpdater.RootObject> releases) - { - var newUpdateLevel = currentUpdateLevel; - - // If the current version is later than current stable, set the update level to beta - if (releases.Count >= 1) - { - var release = releases[0]; - var version = ParseVersion(release.tag_name); - if (version != null) - { - if (currentVersion > version) - { - newUpdateLevel = PackageVersionClass.Beta; - } - else - { - return PackageVersionClass.Release; - } - } - } - - // If the current version is later than current beta, set the update level to dev - if (releases.Count >= 2) - { - var release = releases[1]; - var version = ParseVersion(release.tag_name); - if (version != null) - { - if (currentVersion > version) - { - newUpdateLevel = PackageVersionClass.Dev; - } - else - { - return PackageVersionClass.Beta; - } - } - } - - return newUpdateLevel; - } - - private Version ParseVersion(string versionString) - { - if (!string.IsNullOrWhiteSpace(versionString)) - { - var parts = versionString.Split('.'); - if (parts.Length == 3) - { - versionString += ".0"; - } - } - - Version version; - Version.TryParse(versionString, out version); - - return version; - } - } -} diff --git a/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs b/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs index cc1756f96..0744fc0d9 100644 --- a/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs +++ b/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs @@ -18,15 +18,15 @@ namespace Emby.Server.Implementations.Photos { } - protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item) + protected override List<BaseItem> GetItemsWithImages(IHasImages item) { var photoAlbum = (PhotoAlbum)item; var items = GetFinalItems(photoAlbum.Children.ToList()); - return Task.FromResult(items); + return items; } - protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); } diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index 9514c12ca..127ce24ae 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -10,7 +10,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Emby.Server.Implementations.Images; -using MediaBrowser.Common.IO; + +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; @@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.Playlists { } - protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item) + protected override List<BaseItem> GetItemsWithImages(IHasImages item) { var playlist = (Playlist)item; @@ -67,7 +68,7 @@ namespace Emby.Server.Implementations.Playlists .DistinctBy(i => i.Id) .ToList(); - return Task.FromResult(GetFinalItems(items)); + return GetFinalItems(items); } } @@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Playlists _libraryManager = libraryManager; } - protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item) + protected override List<BaseItem> GetItemsWithImages(IHasImages item) { var items = _libraryManager.GetItemList(new InternalItemsQuery { @@ -89,11 +90,12 @@ namespace Emby.Server.Implementations.Playlists SortBy = new[] { ItemSortBy.Random }, Limit = 4, Recursive = true, - ImageTypes = new[] { ImageType.Primary } + ImageTypes = new[] { ImageType.Primary }, + DtoOptions = new DtoOptions(false) }).ToList(); - return Task.FromResult(GetFinalItems(items)); + return GetFinalItems(items); } //protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) @@ -111,7 +113,7 @@ namespace Emby.Server.Implementations.Playlists _libraryManager = libraryManager; } - protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item) + protected override List<BaseItem> GetItemsWithImages(IHasImages item) { var items = _libraryManager.GetItemList(new InternalItemsQuery { @@ -120,11 +122,12 @@ namespace Emby.Server.Implementations.Playlists SortBy = new[] { ItemSortBy.Random }, Limit = 4, Recursive = true, - ImageTypes = new[] { ImageType.Primary } + ImageTypes = new[] { ImageType.Primary }, + DtoOptions = new DtoOptions(false) }).ToList(); - return Task.FromResult(GetFinalItems(items)); + return GetFinalItems(items); } //protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 18042b587..e0e133e38 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -12,7 +12,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; @@ -135,7 +136,10 @@ namespace Emby.Server.Implementations.Playlists if (options.ItemIdList.Count > 0) { - await AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user); + await AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false) + { + EnableImages = true + }); } return new PlaylistCreationResult @@ -160,21 +164,24 @@ namespace Emby.Server.Implementations.Playlists return path; } - private Task<IEnumerable<BaseItem>> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user) + private IEnumerable<BaseItem> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user, DtoOptions options) { var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null); - return Playlist.GetPlaylistItems(playlistMediaType, items, user); + return Playlist.GetPlaylistItems(playlistMediaType, items, user, options); } public Task AddToPlaylist(string playlistId, IEnumerable<string> itemIds, string userId) { var user = string.IsNullOrWhiteSpace(userId) ? null : _userManager.GetUserById(userId); - return AddToPlaylistInternal(playlistId, itemIds, user); + return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false) + { + EnableImages = true + }); } - private async Task AddToPlaylistInternal(string playlistId, IEnumerable<string> itemIds, User user) + private async Task AddToPlaylistInternal(string playlistId, IEnumerable<string> itemIds, User user, DtoOptions options) { var playlist = _libraryManager.GetItemById(playlistId) as Playlist; @@ -185,12 +192,17 @@ namespace Emby.Server.Implementations.Playlists var list = new List<LinkedChild>(); - var items = (await GetPlaylistItems(itemIds, playlist.MediaType, user).ConfigureAwait(false)) + var items = (GetPlaylistItems(itemIds, playlist.MediaType, user, options)) .Where(i => i.SupportsAddingToPlaylist) .ToList(); foreach (var item in items) { + if (string.IsNullOrWhiteSpace(item.Path)) + { + continue; + } + list.Add(LinkedChild.Create(item)); } @@ -271,7 +283,7 @@ namespace Emby.Server.Implementations.Playlists { var typeName = "PlaylistsFolder"; - return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)) ?? + return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)) ?? _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)); } } diff --git a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index d1c70ba1d..967e7ddd8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -10,7 +10,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Entities; @@ -85,7 +85,9 @@ namespace Emby.Server.Implementations.ScheduledTasks { MediaTypes = new[] { MediaType.Video }, IsFolder = false, - Recursive = true + Recursive = true, + DtoOptions = new DtoOptions(false) + }) .OfType<Video>() .ToList(); diff --git a/Emby.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs b/Emby.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs index 749233fa1..9bf6f2824 100644 --- a/Emby.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs @@ -4,7 +4,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; diff --git a/Emby.Server.Implementations/ServerManager/ServerManager.cs b/Emby.Server.Implementations/ServerManager/ServerManager.cs index 4c9228e54..7cd94c526 100644 --- a/Emby.Server.Implementations/ServerManager/ServerManager.cs +++ b/Emby.Server.Implementations/ServerManager/ServerManager.cs @@ -12,7 +12,7 @@ using System.Collections.Specialized; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; + using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using MediaBrowser.Model.Text; diff --git a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs b/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs index ac20fe7b3..e4392d7e6 100644 --- a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs +++ b/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs @@ -9,7 +9,6 @@ using System.Collections.Specialized; using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using MediaBrowser.Model.Text; diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index 3c2af60db..84dc343c3 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -12,43 +12,28 @@ namespace Emby.Server.Implementations.Services { public static class ResponseHelper { - public static Task WriteToResponse(IResponse httpRes, IRequest httpReq, object result) + public static async Task WriteToResponse(IResponse response, IRequest request, object result, CancellationToken cancellationToken) { if (result == null) { - if (httpRes.StatusCode == (int)HttpStatusCode.OK) + if (response.StatusCode == (int)HttpStatusCode.OK) { - httpRes.StatusCode = (int)HttpStatusCode.NoContent; + response.StatusCode = (int)HttpStatusCode.NoContent; } - httpRes.SetContentLength(0); - return Task.FromResult(true); + response.SetContentLength(0); + return; } var httpResult = result as IHttpResult; if (httpResult != null) { - httpResult.RequestContext = httpReq; - httpReq.ResponseContentType = httpResult.ContentType ?? httpReq.ResponseContentType; - return WriteToResponseInternal(httpRes, httpResult, httpReq); + httpResult.RequestContext = request; + request.ResponseContentType = httpResult.ContentType ?? request.ResponseContentType; } - return WriteToResponseInternal(httpRes, result, httpReq); - } - - /// <summary> - /// Writes to response. - /// Response headers are customizable by implementing IHasHeaders an returning Dictionary of Http headers. - /// </summary> - /// <param name="response">The response.</param> - /// <param name="result">Whether or not it was implicity handled by ServiceStack's built-in handlers.</param> - /// <param name="request">The serialization context.</param> - /// <returns></returns> - private static async Task WriteToResponseInternal(IResponse response, object result, IRequest request) - { var defaultContentType = request.ResponseContentType; - var httpResult = result as IHttpResult; if (httpResult != null) { if (httpResult.RequestContext == null) @@ -105,7 +90,7 @@ namespace Emby.Server.Implementations.Services var asyncStreamWriter = result as IAsyncStreamWriter; if (asyncStreamWriter != null) { - await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); + await asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken).ConfigureAwait(false); return; } @@ -119,7 +104,7 @@ namespace Emby.Server.Implementations.Services var fileWriter = result as FileWriter; if (fileWriter != null) { - await fileWriter.WriteToAsync(response, CancellationToken.None).ConfigureAwait(false); + await fileWriter.WriteToAsync(response, cancellationToken).ConfigureAwait(false); return; } @@ -139,7 +124,7 @@ namespace Emby.Server.Implementations.Services response.ContentType = "application/octet-stream"; response.SetContentLength(bytes.Length); - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); return; } @@ -148,7 +133,7 @@ namespace Emby.Server.Implementations.Services { bytes = Encoding.UTF8.GetBytes(responseText); response.SetContentLength(bytes.Length); - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); return; } diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index d283bf81f..1c530144c 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.Services return null; } - public async Task<object> Execute(HttpListenerHost appHost, object requestDto, IRequest req) + public Task<object> Execute(HttpListenerHost appHost, object requestDto, IRequest req) { req.Dto = requestDto; var requestType = requestDto.GetType(); @@ -209,9 +209,7 @@ namespace Emby.Server.Implementations.Services req.Dto = requestDto; //Executes the service and returns the result - var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()).ConfigureAwait(false); - - return response; + return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()); } } diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 8b59b4843..526e62d39 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Logging; @@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.Services // Set from SSHHF.GetHandlerForPathInfo() public string ResponseContentType { get; set; } - public async Task ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, ILogger logger, string operationName) + public async Task ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, ILogger logger, string operationName, CancellationToken cancellationToken) { var restPath = GetRestPath(httpReq.Verb, httpReq.PathInfo); if (restPath == null) @@ -142,7 +143,8 @@ namespace Emby.Server.Implementations.Services var rawResponse = await appHost.ServiceController.Execute(appHost, request, httpReq).ConfigureAwait(false); - var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false); + //var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false); + var response = rawResponse; // Apply response filters foreach (var responseFilter in appHost.ResponseFilters) @@ -150,7 +152,7 @@ namespace Emby.Server.Implementations.Services responseFilter(httpReq, httpRes, response); } - await ResponseHelper.WriteToResponse(httpRes, httpReq, response).ConfigureAwait(false); + await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false); } public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger) diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index 2acc3902f..92fa6c424 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -66,19 +66,19 @@ namespace Emby.Server.Implementations.Session return SendMessage(name, new Dictionary<string, string>(), cancellationToken); } - private async Task SendMessage(string name, + private Task SendMessage(string name, Dictionary<string, string> args, CancellationToken cancellationToken) { var url = PostUrl + "/" + name + ToQueryString(args); - await _httpClient.Post(new HttpRequestOptions + return _httpClient.Post(new HttpRequestOptions { Url = url, CancellationToken = cancellationToken, BufferContent = false - }).ConfigureAwait(false); + }); } public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) @@ -159,8 +159,24 @@ namespace Emby.Server.Implementations.Session public Task SendMessage<T>(string name, T data, CancellationToken cancellationToken) { - // Not supported or needed right now - return Task.FromResult(true); + var url = PostUrl + "/" + name; + + var options = new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + BufferContent = false + }; + + options.RequestContent = _json.SerializeToString(data); + options.RequestContentType = "application/json"; + + return _httpClient.Post(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + BufferContent = false + }); } private string ToQueryString(Dictionary<string, string> nvc) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index de00cf239..42cd5d1b1 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -30,6 +30,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Threading; namespace Emby.Server.Implementations.Session @@ -984,7 +985,7 @@ namespace Emby.Server.Implementations.Session var list = new List<BaseItem>(); foreach (var itemId in command.ItemIds) { - var subItems = await TranslateItemForPlayback(itemId, user).ConfigureAwait(false); + var subItems = TranslateItemForPlayback(itemId, user); list.AddRange(subItems); } @@ -1022,7 +1023,10 @@ namespace Emby.Server.Implementations.Session var series = episode.Series; if (series != null) { - var episodes = series.GetEpisodes(user) + var episodes = series.GetEpisodes(user, new DtoOptions(false) + { + EnableImages = false + }) .Where(i => !i.IsVirtualItem) .SkipWhile(i => i.Id != episode.Id) .ToList(); @@ -1048,7 +1052,7 @@ namespace Emby.Server.Implementations.Session await session.SessionController.SendPlayCommand(command, cancellationToken).ConfigureAwait(false); } - private async Task<List<BaseItem>> TranslateItemForPlayback(string id, User user) + private List<BaseItem> TranslateItemForPlayback(string id, User user) { var item = _libraryManager.GetItemById(id); @@ -1065,7 +1069,15 @@ namespace Emby.Server.Implementations.Session var items = byName.GetTaggedItems(new InternalItemsQuery(user) { IsFolder = false, - Recursive = true + Recursive = true, + DtoOptions = new DtoOptions(false) + { + EnableImages = false, + Fields = new List<ItemFields> + { + ItemFields.SortName + } + } }); return FilterToSingleMediaType(items) @@ -1077,12 +1089,20 @@ namespace Emby.Server.Implementations.Session { var folder = (Folder)item; - var itemsResult = await folder.GetItems(new InternalItemsQuery(user) + var itemsResult = folder.GetItems(new InternalItemsQuery(user) { Recursive = true, - IsFolder = false + IsFolder = false, + DtoOptions = new DtoOptions(false) + { + EnableImages = false, + Fields = new List<ItemFields> + { + ItemFields.SortName + } + } - }).ConfigureAwait(false); + }); return FilterToSingleMediaType(itemsResult.Items) .OrderBy(i => i.SortName) @@ -1111,7 +1131,7 @@ namespace Emby.Server.Implementations.Session return new List<BaseItem>(); } - return _musicManager.GetInstantMixFromItem(item, user); + return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false }); } public Task SendBrowseCommand(string controllingSessionId, string sessionId, BrowseRequest command, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 478f9da71..2735bb237 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.Session _json = json; _httpServer = httpServer; _serverManager = serverManager; - httpServer.WebSocketConnecting += _httpServer_WebSocketConnecting; serverManager.WebSocketConnected += _serverManager_WebSocketConnected; } @@ -84,27 +83,6 @@ namespace Emby.Server.Implementations.Session } } - async void _httpServer_WebSocketConnecting(object sender, WebSocketConnectingEventArgs e) - { - //var token = e.QueryString["api_key"]; - //if (!string.IsNullOrWhiteSpace(token)) - //{ - // try - // { - // var session = await GetSession(e.QueryString, e.Endpoint).ConfigureAwait(false); - - // if (session == null) - // { - // e.AllowConnection = false; - // } - // } - // catch (Exception ex) - // { - // _logger.ErrorException("Error getting session info", ex); - // } - //} - } - private Task<SessionInfo> GetSession(QueryParamCollection queryString, string remoteEndpoint) { if (queryString == null) @@ -123,7 +101,6 @@ namespace Emby.Server.Implementations.Session public void Dispose() { - _httpServer.WebSocketConnecting -= _httpServer_WebSocketConnecting; _serverManager.WebSocketConnected -= _serverManager_WebSocketConnected; } diff --git a/Emby.Server.Implementations/TV/SeriesPostScanTask.cs b/Emby.Server.Implementations/TV/SeriesPostScanTask.cs index 3d93561f1..23b6a3cb5 100644 --- a/Emby.Server.Implementations/TV/SeriesPostScanTask.cs +++ b/Emby.Server.Implementations/TV/SeriesPostScanTask.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -58,7 +59,8 @@ namespace Emby.Server.Implementations.TV { IncludeItemTypes = new[] { typeof(Series).Name }, Recursive = true, - GroupByPresentationUniqueKey = false + GroupByPresentationUniqueKey = false, + DtoOptions = new DtoOptions(true) }).Cast<Series>().ToList(); @@ -188,7 +190,8 @@ namespace Emby.Server.Implementations.TV { IncludeItemTypes = new[] { typeof(Series).Name }, Recursive = true, - GroupByPresentationUniqueKey = false + GroupByPresentationUniqueKey = false, + DtoOptions = new DtoOptions(true) }).Cast<Series>().ToList(); diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index b5e64bc23..876c5d58b 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; namespace Emby.Server.Implementations.TV { @@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.TV _config = config; } - public QueryResult<BaseItem> GetNextUp(NextUpQuery request) + public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions) { var user = _userManager.GetUserById(request.UserId); @@ -68,19 +69,19 @@ namespace Emby.Server.Implementations.TV { Fields = new List<ItemFields> { - + ItemFields.PresentationUniqueKey } } }).Cast<Series>().Select(GetUniqueSeriesKey); // Avoid implicitly captured closure - var episodes = GetNextUpEpisodes(request, user, items); + var episodes = GetNextUpEpisodes(request, user, items, dtoOptions); return GetResult(episodes, request); } - public QueryResult<BaseItem> GetNextUp(NextUpQuery request, List<Folder> parentsFolders) + public QueryResult<BaseItem> GetNextUp(NextUpQuery request, List<Folder> parentsFolders, DtoOptions dtoOptions) { var user = _userManager.GetUserById(request.UserId); @@ -118,7 +119,7 @@ namespace Emby.Server.Implementations.TV { Fields = new List<ItemFields> { - + ItemFields.PresentationUniqueKey }, EnableImages = false } @@ -126,18 +127,18 @@ namespace Emby.Server.Implementations.TV }, parentsFolders.Cast<BaseItem>().ToList()).Cast<Series>().Select(GetUniqueSeriesKey); // Avoid implicitly captured closure - var episodes = GetNextUpEpisodes(request, user, items); + var episodes = GetNextUpEpisodes(request, user, items, dtoOptions); return GetResult(episodes, request); } - public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys) + public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys, DtoOptions dtoOptions) { // Avoid implicitly captured closure var currentUser = user; var allNextUp = seriesKeys - .Select(i => GetNextUp(i, currentUser)); + .Select(i => GetNextUp(i, currentUser, dtoOptions)); //allNextUp = allNextUp.OrderByDescending(i => i.Item1); @@ -175,14 +176,12 @@ namespace Emby.Server.Implementations.TV /// Gets the next up. /// </summary> /// <returns>Task{Episode}.</returns> - private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user) + private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions) { - var enableSeriesPresentationKey = _config.Configuration.EnableSeriesPresentationUniqueKey; - var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey, - SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null, + AncestorWithPresentationUniqueKey = null, + SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name }, SortBy = new[] { ItemSortBy.SortName }, SortOrder = SortOrder.Descending, @@ -193,7 +192,7 @@ namespace Emby.Server.Implementations.TV { Fields = new List<ItemFields> { - + ItemFields.SortName }, EnableImages = false } @@ -204,8 +203,8 @@ namespace Emby.Server.Implementations.TV { return _libraryManager.GetItemList(new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey, - SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null, + AncestorWithPresentationUniqueKey = null, + SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name }, SortBy = new[] { ItemSortBy.SortName }, SortOrder = SortOrder.Ascending, @@ -213,7 +212,8 @@ namespace Emby.Server.Implementations.TV IsPlayed = false, IsVirtualItem = false, ParentIndexNumberNotEquals = 0, - MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName + MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName, + DtoOptions = dtoOptions }).Cast<Episode>().FirstOrDefault(); }; diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 21ef3cab6..8dc1fae4b 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -139,30 +139,58 @@ namespace Emby.Server.Implementations.Udp { _udpClient = _socketFactory.CreateUdpSocket(port); - Task.Run(() => StartListening()); + Task.Run(() => BeginReceive()); } - private async void StartListening() + private readonly byte[] _receiveBuffer = new byte[8192]; + + private void BeginReceive() { - while (!_isDisposed) + if (_isDisposed) { - try - { - var result = await _udpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false); + return; + } - OnMessageReceived(result); - } - catch (ObjectDisposedException) - { - } - catch (OperationCanceledException) - { - } - catch (Exception ex) + try + { + var result = _udpClient.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, OnReceiveResult); + + if (result.CompletedSynchronously) { - _logger.ErrorException("Error receiving udp message", ex); + OnReceiveResult(result); } } + catch (ObjectDisposedException) + { + } + catch (Exception ex) + { + _logger.ErrorException("Error receiving udp message", ex); + } + } + + private void OnReceiveResult(IAsyncResult result) + { + if (_isDisposed) + { + return; + } + + try + { + var socketResult = _udpClient.EndReceive(result); + + OnMessageReceived(socketResult); + } + catch (ObjectDisposedException) + { + } + catch (Exception ex) + { + _logger.ErrorException("Error receiving udp message", ex); + } + + BeginReceive(); } /// <summary> @@ -239,13 +267,13 @@ namespace Emby.Server.Implementations.Udp try { - await _udpClient.SendWithLockAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false); + await _udpClient.SendToAsync(bytes, 0, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false); _logger.Info("Udp message sent to {0}", remoteEndPoint); } catch (OperationCanceledException) { - + } catch (Exception ex) { diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index ab6307238..f54613384 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -11,9 +11,9 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Emby.Server.Implementations.Images; -using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; @@ -36,19 +36,20 @@ namespace Emby.Server.Implementations.UserViews }; } - protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item) + protected override List<BaseItem> GetItemsWithImages(IHasImages item) { var view = (CollectionFolder)item; var recursive = !new[] { CollectionType.Playlists, CollectionType.Channels }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); - var result = await view.GetItems(new InternalItemsQuery + var result = view.GetItems(new InternalItemsQuery { CollapseBoxSetItems = false, Recursive = recursive, - ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Playlist" } + ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Playlist" }, + DtoOptions = new DtoOptions(false) - }).ConfigureAwait(false); + }); var items = result.Items.Select(i => { @@ -98,7 +99,7 @@ namespace Emby.Server.Implementations.UserViews return item is CollectionFolder; } - protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); @@ -109,10 +110,10 @@ namespace Emby.Server.Implementations.UserViews return null; } - return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false); + return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540); } - return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false); + return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex); } } @@ -133,7 +134,7 @@ namespace Emby.Server.Implementations.UserViews }; } - protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item) + protected override List<BaseItem> GetItemsWithImages(IHasImages item) { var view = (ManualCollectionsFolder)item; @@ -144,7 +145,8 @@ namespace Emby.Server.Implementations.UserViews Recursive = recursive, IncludeItemTypes = new[] { typeof(BoxSet).Name }, Limit = 20, - SortBy = new[] { ItemSortBy.Random } + SortBy = new[] { ItemSortBy.Random }, + DtoOptions = new DtoOptions(false) }); return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8); @@ -155,7 +157,7 @@ namespace Emby.Server.Implementations.UserViews return item is ManualCollectionsFolder; } - protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); @@ -166,10 +168,10 @@ namespace Emby.Server.Implementations.UserViews return null; } - return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false); + return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540); } - return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false); + return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex); } } diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index b8d03db3d..cd2c4728f 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -12,6 +12,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Emby.Server.Implementations.Images; +using MediaBrowser.Controller.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Extensions; @@ -47,7 +48,7 @@ namespace Emby.Server.Implementations.UserViews }; } - protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item) + protected override List<BaseItem> GetItemsWithImages(IHasImages item) { var view = (UserView)item; @@ -58,7 +59,9 @@ namespace Emby.Server.Implementations.UserViews IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, ImageTypes = new[] { ImageType.Primary }, Limit = 30, - IsMovie = true + IsMovie = true, + DtoOptions = new DtoOptions(false) + }).ToList(); return GetFinalItems(programs).ToList(); @@ -67,9 +70,10 @@ namespace Emby.Server.Implementations.UserViews if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) || string.Equals(view.ViewType, SpecialFolder.TvGenre, StringComparison.OrdinalIgnoreCase)) { - var userItemsResult = await view.GetItems(new InternalItemsQuery + var userItemsResult = view.GetItems(new InternalItemsQuery { - CollapseBoxSetItems = false + CollapseBoxSetItems = false, + DtoOptions = new DtoOptions(false) }); return userItemsResult.Items.ToList(); @@ -78,14 +82,14 @@ namespace Emby.Server.Implementations.UserViews var isUsingCollectionStrip = IsUsingCollectionStrip(view); var recursive = isUsingCollectionStrip && !new[] { CollectionType.Channels, CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase); - var result = await view.GetItems(new InternalItemsQuery + var result = view.GetItems(new InternalItemsQuery { User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null, CollapseBoxSetItems = false, Recursive = recursive, ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" }, - - }).ConfigureAwait(false); + DtoOptions = new DtoOptions(false) + }); var items = result.Items.Select(i => { @@ -159,7 +163,7 @@ namespace Emby.Server.Implementations.UserViews return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty); } - protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { if (itemsWithImages.Count == 0) { @@ -168,7 +172,7 @@ namespace Emby.Server.Implementations.UserViews var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); - return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false); + return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540); } } } |
