diff options
Diffstat (limited to 'Emby.Server.Implementations')
93 files changed, 1525 insertions, 1856 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 1b6daca73..4e448ac64 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -491,6 +491,7 @@ namespace Emby.Server.Implementations.Activity //_logManager.LoggerLoaded -= _logManager_LoggerLoaded; _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index fcc637b25..d3950929d 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -182,8 +182,7 @@ namespace Emby.Server.Implementations.Channels { }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) - .ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -427,6 +426,8 @@ namespace Emby.Server.Implementations.Channels item.Name = channelInfo.Name; } + item.OnMetadataChanged(); + if (isNew) { _libraryManager.CreateItem(item, cancellationToken); @@ -467,7 +468,7 @@ namespace Emby.Server.Implementations.Channels return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Channel).Name }, - SortBy = new[] { ItemSortBy.SortName } + OrderBy = new Tuple<string, SortOrder>[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) } }).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray(); } @@ -567,7 +568,7 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields }; - var returnItems = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -832,8 +833,7 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) - .ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -934,14 +934,15 @@ namespace Emby.Server.Implementations.Channels ChannelItemSortField? sortField = null; ChannelItemSortField parsedField; - if (query.SortBy.Length == 1 && - Enum.TryParse(query.SortBy[0], true, out parsedField)) + var sortDescending = false; + + if (query.OrderBy.Length == 1 && + Enum.TryParse(query.OrderBy[0].Item1, true, out parsedField)) { sortField = parsedField; + sortDescending = query.OrderBy[0].Item2 == SortOrder.Descending; } - var sortDescending = query.SortOrder.HasValue && query.SortOrder.Value == SortOrder.Descending; - var itemsResult = await GetChannelItems(channelProvider, user, query.FolderId, @@ -984,8 +985,7 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) - .ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -1169,7 +1169,7 @@ namespace Emby.Server.Implementations.Channels { items = ApplyFilters(items, query.Filters, user); - items = _libraryManager.Sort(items, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending); + items = _libraryManager.Sort(items, user, query.OrderBy); var all = items.ToList(); var totalCount = totalCountFromProvider ?? all.Count; @@ -1386,6 +1386,8 @@ namespace Emby.Server.Implementations.Channels item.SetImagePath(ImageType.Primary, info.ImageUrl); } + item.OnMetadataChanged(); + if (isNew) { _libraryManager.CreateItem(item, cancellationToken); @@ -1626,6 +1628,7 @@ namespace Emby.Server.Implementations.Channels public void Dispose() { + GC.SuppressFinalize(this); } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index c7378956d..f47e2d10a 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.Collections return subItem; } - var parent = subItem.GetParent(); + var parent = subItem.IsOwnedItem ? subItem.GetOwner() : subItem.GetParent(); if (parent != null && parent.HasImage(ImageType.Primary)) { diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 91a2dfdf6..5d0fc8ebc 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -77,6 +77,7 @@ namespace Emby.Server.Implementations.Data { Close(); } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 74e009bd9..89ffb0fce 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -139,34 +139,34 @@ namespace Emby.Server.Implementations.Data RunDefaultInitialization(connection); var createMediaStreamsTableCommand - = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; + = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; string[] queries = { - "PRAGMA locking_mode=EXCLUSIVE", + "PRAGMA locking_mode=EXCLUSIVE", - "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)", + "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)", - "create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))", - "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", - "create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)", + "create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))", + "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", + "create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)", - "create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT, CleanValue TEXT)", + "create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT, CleanValue TEXT)", - "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)", + "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)", - "drop index if exists idxPeopleItemId", - "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)", - "create index if not exists idxPeopleName on People(Name)", + "drop index if exists idxPeopleItemId", + "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)", + "create index if not exists idxPeopleName on People(Name)", - "create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", + "create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", - createMediaStreamsTableCommand, + createMediaStreamsTableCommand, - "create index if not exists idx_mediastreams1 on mediastreams(ItemId)", + "create index if not exists idx_mediastreams1 on mediastreams(ItemId)", - "pragma shrink_memory" + "pragma shrink_memory" - }; + }; connection.RunQueries(queries); @@ -252,6 +252,8 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "AlbumArtists", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ExternalId", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames); + AddColumn(db, "TypedBaseItems", "ShowId", "Text", existingColumnNames); + AddColumn(db, "TypedBaseItems", "OwnerId", "Text", existingColumnNames); existingColumnNames = GetColumnNames(db, "ItemValues"); AddColumn(db, "ItemValues", "CleanValue", "Text", existingColumnNames); @@ -276,80 +278,80 @@ namespace Emby.Server.Implementations.Data string[] postQueries = - { - // obsolete - "drop index if exists idx_TypedBaseItems", - "drop index if exists idx_mediastreams", - "drop index if exists idx_"+ChaptersTableName, - "drop index if exists idx_UserDataKeys1", - "drop index if exists idx_UserDataKeys2", - "drop index if exists idx_TypeTopParentId3", - "drop index if exists idx_TypeTopParentId2", - "drop index if exists idx_TypeTopParentId4", - "drop index if exists idx_Type", - "drop index if exists idx_TypeTopParentId", - "drop index if exists idx_GuidType", - "drop index if exists idx_TopParentId", - "drop index if exists idx_TypeTopParentId6", - "drop index if exists idx_ItemValues2", - "drop index if exists Idx_ProviderIds", - "drop index if exists idx_ItemValues3", - "drop index if exists idx_ItemValues4", - "drop index if exists idx_ItemValues5", - "drop index if exists idx_UserDataKeys3", - "drop table if exists UserDataKeys", - "drop table if exists ProviderIds", - "drop index if exists Idx_ProviderIds1", - "drop table if exists Images", - "drop index if exists idx_Images", - "drop index if exists idx_TypeSeriesPresentationUniqueKey", - "drop index if exists idx_SeriesPresentationUniqueKey", - "drop index if exists idx_TypeSeriesPresentationUniqueKey2", - "drop index if exists idx_AncestorIds3", - "drop index if exists idx_AncestorIds4", - "drop index if exists idx_AncestorIds2", - - "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", - "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", - - "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", - "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", - //"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)", - "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", - - // covering index - "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", - - // series - "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)", - - // series counts - // seriesdateplayed sort order - "create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)", - - // live tv programs - "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", - - // covering index for getitemvalues - "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)", - - // used by movie suggestions - "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)", - "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)", - - // latest items - "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)", - "create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)", - - // resume - "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)", - - // items by name - "create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)", - "create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)", - - // Used to update inherited tags - "create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)", + { + // obsolete + "drop index if exists idx_TypedBaseItems", + "drop index if exists idx_mediastreams", + "drop index if exists idx_"+ChaptersTableName, + "drop index if exists idx_UserDataKeys1", + "drop index if exists idx_UserDataKeys2", + "drop index if exists idx_TypeTopParentId3", + "drop index if exists idx_TypeTopParentId2", + "drop index if exists idx_TypeTopParentId4", + "drop index if exists idx_Type", + "drop index if exists idx_TypeTopParentId", + "drop index if exists idx_GuidType", + "drop index if exists idx_TopParentId", + "drop index if exists idx_TypeTopParentId6", + "drop index if exists idx_ItemValues2", + "drop index if exists Idx_ProviderIds", + "drop index if exists idx_ItemValues3", + "drop index if exists idx_ItemValues4", + "drop index if exists idx_ItemValues5", + "drop index if exists idx_UserDataKeys3", + "drop table if exists UserDataKeys", + "drop table if exists ProviderIds", + "drop index if exists Idx_ProviderIds1", + "drop table if exists Images", + "drop index if exists idx_Images", + "drop index if exists idx_TypeSeriesPresentationUniqueKey", + "drop index if exists idx_SeriesPresentationUniqueKey", + "drop index if exists idx_TypeSeriesPresentationUniqueKey2", + "drop index if exists idx_AncestorIds3", + "drop index if exists idx_AncestorIds4", + "drop index if exists idx_AncestorIds2", + + "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", + "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", + + "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", + "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", + //"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)", + "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", + + // covering index + "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", + + // series + "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)", + + // series counts + // seriesdateplayed sort order + "create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)", + + // live tv programs + "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", + + // covering index for getitemvalues + "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)", + + // used by movie suggestions + "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)", + "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)", + + // latest items + "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)", + "create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)", + + // resume + "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)", + + // items by name + "create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)", + "create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)", + + // Used to update inherited tags + "create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)", }; connection.RunQueries(postQueries); @@ -457,7 +459,9 @@ namespace Emby.Server.Implementations.Data "Artists", "AlbumArtists", "ExternalId", - "SeriesPresentationUniqueKey" + "SeriesPresentationUniqueKey", + "ShowId", + "OwnerId" }; private readonly string[] _mediaStreamSaveColumns = @@ -577,7 +581,9 @@ namespace Emby.Server.Implementations.Data "Artists", "AlbumArtists", "ExternalId", - "SeriesPresentationUniqueKey" + "SeriesPresentationUniqueKey", + "ShowId", + "OwnerId" }; var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -781,13 +787,14 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@PremiereDate", item.PremiereDate); saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); - if (item.ParentId == Guid.Empty) + var parentId = item.ParentId; + if (parentId == Guid.Empty) { saveItemStatement.TryBindNull("@ParentId"); } else { - saveItemStatement.TryBind("@ParentId", item.ParentId); + saveItemStatement.TryBind("@ParentId", parentId); } if (item.Genres.Count > 0) @@ -1044,6 +1051,26 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@AlbumArtists", albumArtists); saveItemStatement.TryBind("@ExternalId", item.ExternalId); + var program = item as LiveTvProgram; + if (program != null) + { + saveItemStatement.TryBind("@ShowId", program.ShowId); + } + else + { + saveItemStatement.TryBindNull("@ShowId"); + } + + var ownerId = item.OwnerId; + if (ownerId != Guid.Empty) + { + saveItemStatement.TryBind("@OwnerId", ownerId); + } + else + { + saveItemStatement.TryBindNull("@OwnerId"); + } + saveItemStatement.MoveNext(); } @@ -1140,19 +1167,17 @@ namespace Emby.Server.Implementations.Data } return path + - delimeter + - image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + - delimeter + - image.Type + - delimeter + - image.IsPlaceholder; + delimeter + + image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + + delimeter + + image.Type; } public ItemImageInfo ItemImageInfoFromValueString(string value) { var parts = value.Split(new[] { '*' }, StringSplitOptions.None); - if (parts.Length != 4) + if (parts.Length < 3) { return null; } @@ -1160,9 +1185,18 @@ namespace Emby.Server.Implementations.Data var image = new ItemImageInfo(); image.Path = parts[0]; - image.DateModified = new DateTime(long.Parse(parts[1], CultureInfo.InvariantCulture), DateTimeKind.Utc); - image.Type = (ImageType)Enum.Parse(typeof(ImageType), parts[2], true); - image.IsPlaceholder = string.Equals(parts[3], true.ToString(), StringComparison.OrdinalIgnoreCase); + + long ticks; + if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out ticks)) + { + image.DateModified = new DateTime(ticks, DateTimeKind.Utc); + } + + ImageType type; + if (Enum.TryParse(parts[2], true, out type)) + { + image.Type = type; + } return image; } @@ -1696,17 +1730,17 @@ namespace Emby.Server.Implementations.Data if (!reader.IsDBNull(index)) { trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select( - i => - { - TrailerType parsedValue; - - if (Enum.TryParse(i, true, out parsedValue)) + i => { - return parsedValue; - } - return (TrailerType?)null; + TrailerType parsedValue; + + if (Enum.TryParse(i, true, out parsedValue)) + { + return parsedValue; + } + return (TrailerType?)null; - }).Where(i => i.HasValue).Select(i => i.Value).ToList(); + }).Where(i => i.HasValue).Select(i => i.Value).ToList(); } } index++; @@ -1935,6 +1969,29 @@ namespace Emby.Server.Implementations.Data index++; } + if (enableProgramAttributes) + { + var program = item as LiveTvProgram; + if (program != null) + { + if (!reader.IsDBNull(index)) + { + program.ShowId = reader.GetString(index); + } + index++; + } + else + { + index++; + } + } + + if (!reader.IsDBNull(index)) + { + item.OwnerId = reader.GetGuid(index); + } + index++; + return item; } @@ -2135,8 +2192,7 @@ namespace Emby.Server.Implementations.Data //return true; } - var sortingFields = query.SortBy.ToList(); - sortingFields.AddRange(query.OrderBy.Select(i => i.Item1)); + var sortingFields = query.OrderBy.Select(i => i.Item1).ToList(); if (sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)) { @@ -2192,8 +2248,8 @@ namespace Emby.Server.Implementations.Data } private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToList(); + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); private IEnumerable<string> GetColumnNamesFromField(ItemFields field) { @@ -2295,13 +2351,13 @@ namespace Emby.Server.Implementations.Data private bool HasStartDate(InternalItemsQuery query) { var excludeParentTypes = new string[] - { + { "Series", "Season", "MusicAlbum", "MusicArtist", "PhotoAlbum" - }; + }; if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { @@ -2358,11 +2414,11 @@ namespace Emby.Server.Implementations.Data private bool HasArtistFields(InternalItemsQuery query) { var excludeParentTypes = new string[] - { + { "Series", "Season", "PhotoAlbum" - }; + }; if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { @@ -2391,9 +2447,9 @@ namespace Emby.Server.Implementations.Data private bool HasSeriesFields(InternalItemsQuery query) { var excludeParentTypes = new string[] - { + { "PhotoAlbum" - }; + }; if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { @@ -2442,6 +2498,7 @@ namespace Emby.Server.Implementations.Data list.Remove("IsPremiere"); list.Remove("EpisodeTitle"); list.Remove("IsRepeat"); + list.Remove("ShowId"); } if (!HasEpisodeAttributes(query)) @@ -2975,16 +3032,7 @@ namespace Emby.Server.Implementations.Data private string GetOrderByText(InternalItemsQuery query) { var orderBy = query.OrderBy.ToList(); - var enableOrderInversion = true; - - if (orderBy.Count == 0) - { - orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder))); - } - else - { - enableOrderInversion = false; - } + var enableOrderInversion = false; if (query.SimilarTo != null) { @@ -2993,12 +3041,10 @@ namespace Emby.Server.Implementations.Data orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)); orderBy.Add(new Tuple<string, SortOrder>("SimilarityScore", SortOrder.Descending)); //orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)); - query.SortOrder = SortOrder.Descending; - enableOrderInversion = false; } } - query.OrderBy = orderBy; + query.OrderBy = orderBy.ToArray(); if (orderBy.Count == 0) { @@ -4235,6 +4281,54 @@ namespace Emby.Server.Implementations.Data } } + if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage)) + { + whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)"); + if (statement != null) + { + statement.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage); + } + } + + if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage)) + { + whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)"); + if (statement != null) + { + statement.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage); + } + } + + if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage)) + { + whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)"); + if (statement != null) + { + statement.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage); + } + } + + if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage)) + { + whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)"); + if (statement != null) + { + statement.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage); + } + } + + if (query.HasChapterImages.HasValue) + { + if (query.HasChapterImages.Value) + { + whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) not null)"); + } + else + { + whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) is null)"); + } + } + if (query.HasDeadParentId.HasValue && query.HasDeadParentId.Value) { whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)"); @@ -4316,7 +4410,7 @@ namespace Emby.Server.Implementations.Data index++; } - whereClauses.Add(string.Join(" OR ", includeIds.ToArray())); + whereClauses.Add("(" + string.Join(" OR ", includeIds.ToArray()) + ")"); } if (query.ExcludeItemIds.Length > 0) { @@ -4400,7 +4494,6 @@ namespace Emby.Server.Implementations.Data } } - var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList(); var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; @@ -4654,63 +4747,24 @@ namespace Emby.Server.Implementations.Data private void UpdateInheritedTags(CancellationToken cancellationToken) { - var newValues = new List<Tuple<Guid, string[]>>(); - - var commandText = @"select guid, -(select group_concat(Value, '|') from ItemValues where (ItemValues.ItemId = Outer.Guid OR ItemValues.ItemId in ((Select AncestorId from AncestorIds where AncestorIds.ItemId=Outer.guid))) and ItemValues.Type = 4) NewInheritedTags, -(select group_concat(Value, '|') from ItemValues where ItemValues.ItemId = Outer.Guid and ItemValues.Type = 6) CurrentInheritedTags -from typedbaseitems as Outer -where (NewInheritedTags <> CurrentInheritedTags or (NewInheritedTags is null) <> (CurrentInheritedTags is null)) -limit 100"; - using (WriteLock.Write()) { using (var connection = CreateConnection()) { connection.RunInTransaction(db => { - foreach (var row in connection.Query(commandText)) - { - var id = row.GetGuid(0); - string value = row.IsDBNull(1) ? null : row.GetString(1); - - var valuesArray = string.IsNullOrWhiteSpace(value) ? new string[] { } : value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - - newValues.Add(new Tuple<Guid, string[]>(id, valuesArray)); - } - - Logger.Debug("UpdateInheritedTags - {0} rows", newValues.Count); - if (newValues.Count == 0) + connection.ExecuteAll(string.Join(";", new string[] { - return; - } - - using (var insertStatement = PrepareStatement(connection, "insert into ItemValues (ItemId, Type, Value, CleanValue) values (@ItemId, 6, @Value, @CleanValue)")) - { - using (var deleteStatement = PrepareStatement(connection, "delete from ItemValues where ItemId=@ItemId and Type=6")) - { - foreach (var item in newValues) - { - var guidBlob = item.Item1.ToGuidBlob(); + "delete from itemvalues where type = 6", - deleteStatement.Reset(); - deleteStatement.TryBind("@ItemId", guidBlob); - deleteStatement.MoveNext(); + "insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4", - foreach (var itemValue in item.Item2) - { - insertStatement.Reset(); - - insertStatement.TryBind("@ItemId", guidBlob); - insertStatement.TryBind("@Value", itemValue); + @"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue +FROM AncestorIds +LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId) +where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 " - insertStatement.TryBind("@CleanValue", GetCleanValue(itemValue)); - - insertStatement.MoveNext(); - } - } - } - } + })); }, TransactionMode); } @@ -5503,7 +5557,7 @@ limit 100"; var listIndex = 0; using (var statement = PrepareStatement(connection, - "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values (@ItemId, @Name, @Role, @PersonType, @SortOrder, @ListOrder)")) + "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values (@ItemId, @Name, @Role, @PersonType, @SortOrder, @ListOrder)")) { foreach (var person in people) { @@ -5632,8 +5686,8 @@ limit 100"; connection.Execute("delete from mediastreams where ItemId=@ItemId", id.ToGuidBlob()); using (var statement = PrepareStatement(connection, string.Format("replace into mediastreams ({0}) values ({1})", - string.Join(",", _mediaStreamSaveColumns), - string.Join(",", _mediaStreamSaveColumns.Select(i => "@" + i).ToArray())))) + string.Join(",", _mediaStreamSaveColumns), + string.Join(",", _mediaStreamSaveColumns.Select(i => "@" + i).ToArray())))) { foreach (var stream in streams) { diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 09b8bf22c..a0a5f32ef 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -104,6 +104,7 @@ namespace Emby.Server.Implementations.Diagnostics public void Dispose() { _process.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index a0e994d88..c5fcc0e4c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -87,17 +87,17 @@ namespace Emby.Server.Implementations.Dto return GetBaseItemDto(item, options, user, owner); } - public Task<BaseItemDto[]> GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) + public BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) { return GetBaseItemDtos(items, items.Count, options, user, owner); } - public Task<BaseItemDto[]> GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null) + public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null) { return GetBaseItemDtos(items, items.Length, options, user, owner); } - public async Task<BaseItemDto[]> GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) + public BaseItemDto[] GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) { if (items == null) { @@ -157,12 +157,13 @@ namespace Emby.Server.Implementations.Dto if (programTuples.Count > 0) { - await _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).ConfigureAwait(false); + var task = _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user); + Task.WaitAll(task); } if (channelTuples.Count > 0) { - await _livetvManager().AddChannelInfo(channelTuples, options, user).ConfigureAwait(false); + _livetvManager().AddChannelInfo(channelTuples, options, user); } return returnItems; @@ -177,8 +178,7 @@ namespace Emby.Server.Implementations.Dto if (tvChannel != null) { var list = new List<Tuple<BaseItemDto, LiveTvChannel>> { new Tuple<BaseItemDto, LiveTvChannel>(dto, tvChannel) }; - var task = _livetvManager().AddChannelInfo(list, options, user); - Task.WaitAll(task); + _livetvManager().AddChannelInfo(list, options, user); } else if (item is LiveTvProgram) { @@ -275,8 +275,7 @@ namespace Emby.Server.Implementations.Dto { var hasFullSyncInfo = options.Fields.Contains(ItemFields.SyncInfo); - if (!options.Fields.Contains(ItemFields.BasicSyncInfo) && - !hasFullSyncInfo) + if (!hasFullSyncInfo && !options.Fields.Contains(ItemFields.BasicSyncInfo)) { return; } @@ -418,6 +417,8 @@ namespace Emby.Server.Implementations.Dto { dto.Type = "Recording"; dto.CanDownload = false; + dto.RunTimeTicks = null; + if (!string.IsNullOrWhiteSpace(dto.SeriesName)) { dto.EpisodeTitle = dto.Name; @@ -1486,7 +1487,7 @@ namespace Emby.Server.Implementations.Dto } } - var parent = currentItem.DisplayParent ?? currentItem.GetParent(); + var parent = currentItem.DisplayParent ?? (currentItem.IsOwnedItem ? currentItem.GetOwner() : currentItem.GetParent()); if (parent == null && !(originalItem is UserRootFolder) && !(originalItem is UserView) && !(originalItem is AggregateFolder) && !(originalItem is ICollectionFolder) && !(originalItem is Channel)) { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 84ec214c9..ccff29eef 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -129,7 +129,6 @@ <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\IsoManager.cs" /> <Compile Include="IO\LibraryMonitor.cs" /> @@ -440,7 +439,6 @@ <Compile Include="Networking\NetworkManager.cs" /> <Compile Include="Net\DisposableManagedObjectBase.cs" /> <Compile Include="Net\NetAcceptSocket.cs" /> - <Compile Include="Net\SocketAcceptor.cs" /> <Compile Include="Net\SocketFactory.cs" /> <Compile Include="Net\UdpSocket.cs" /> <Compile Include="News\NewsEntryPoint.cs" /> @@ -497,6 +495,7 @@ <Compile Include="Services\ServiceController.cs" /> <Compile Include="Services\ServiceExec.cs" /> <Compile Include="Services\StringMapTypeDeserializer.cs" /> + <Compile Include="Services\SwaggerService.cs" /> <Compile Include="Services\UrlExtensions.cs" /> <Compile Include="Session\HttpSessionController.cs" /> <Compile Include="Session\SessionManager.cs" /> @@ -655,16 +654,15 @@ <Project>{1d74413b-e7cf-455b-b021-f52bdf881542}</Project> <Name>SocketHttpListener</Name> </ProjectReference> + <Reference Include="Emby.Naming"> + <HintPath>..\ThirdParty\emby\Emby.Naming.dll</HintPath> + </Reference> <Reference Include="Emby.Server.MediaEncoding"> <HintPath>..\ThirdParty\emby\Emby.Server.MediaEncoding.dll</HintPath> </Reference> <Reference Include="Emby.XmlTv, Version=1.0.6387.29335, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\Emby.XmlTv.1.0.10\lib\portable-net45+netstandard2.0+win8\Emby.XmlTv.dll</HintPath> </Reference> - <Reference Include="MediaBrowser.Naming, Version=1.0.6447.2217, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\MediaBrowser.Naming.1.0.7\lib\portable-net45+netstandard2.0+win8\MediaBrowser.Naming.dll</HintPath> - <Private>True</Private> - </Reference> <Reference Include="ServiceStack.Text, Version=4.5.8.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\ServiceStack.Text.4.5.8\lib\net45\ServiceStack.Text.dll</HintPath> <Private>True</Private> diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs index 561f5ee12..c2cee00c8 100644 --- a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs @@ -112,6 +112,7 @@ namespace Emby.Server.Implementations.EntryPoints _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; DisposeTimer(); + GC.SuppressFinalize(this); } private void DisposeTimer() diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index c96799b2f..9b434d606 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -294,6 +294,7 @@ namespace Emby.Server.Implementations.EntryPoints { _disposed = true; DisposeNat(); + GC.SuppressFinalize(this); } private void DisposeNat() diff --git a/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs b/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs index 8ae85e390..221580681 100644 --- a/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs +++ b/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs @@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.EntryPoints _timer.Dispose(); _timer = null; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 80a188bc0..299da0744 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -198,9 +198,10 @@ namespace Emby.Server.Implementations.EntryPoints LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); } - if (e.Item.Parent != null) + var parent = e.Item.GetParent() as Folder; + if (parent != null) { - _foldersAddedTo.Add(e.Item.Parent); + _foldersAddedTo.Add(parent); } _itemsAdded.Add(e.Item); @@ -259,9 +260,10 @@ namespace Emby.Server.Implementations.EntryPoints LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); } - if (e.Item.Parent != null) + var parent = e.Item.GetParent() as Folder; + if (parent != null) { - _foldersRemovedFrom.Add(e.Item.Parent); + _foldersRemovedFrom.Add(parent); } _itemsRemoved.Add(e.Item); @@ -426,6 +428,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs index 0203b5192..21e075cf5 100644 --- a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs +++ b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs @@ -68,6 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints _timer.Dispose(); _timer = null; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index b674fc39b..f73b40b46 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -72,6 +72,7 @@ namespace Emby.Server.Implementations.EntryPoints _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled; _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated; _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs index 77de849a1..4c16b1d39 100644 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs @@ -1,41 +1,74 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; +using System; +using MediaBrowser.Controller.Library; using System.Threading; +using MediaBrowser.Model.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.EntryPoints { /// <summary> /// Class RefreshUsersMetadata /// </summary> - public class RefreshUsersMetadata : IServerEntryPoint + public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask { /// <summary> /// The _user manager /// </summary> private readonly IUserManager _userManager; + private IFileSystem _fileSystem; + + public string Name => "Refresh Users"; + + public string Key => "RefreshUsers"; + + public string Description => "Refresh user infos"; + + public string Category + { + get { return "Library"; } + } + + public bool IsHidden => true; + + public bool IsEnabled => true; + + public bool IsLogged => true; /// <summary> /// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class. /// </summary> - /// <param name="userManager">The user manager.</param> - public RefreshUsersMetadata(IUserManager userManager) + public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem) { _userManager = userManager; + _fileSystem = fileSystem; } - /// <summary> - /// Runs this instance. - /// </summary> - public async void Run() + public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { - await _userManager.RefreshUsersMetadata(CancellationToken.None).ConfigureAwait(false); + var users = _userManager.Users.ToList(); + + foreach (var user in users) + { + cancellationToken.ThrowIfCancellationRequested(); + + await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken).ConfigureAwait(false); + } } - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { + return new List<TaskTriggerInfo> + { + new TaskTriggerInfo + { + IntervalTicks = TimeSpan.FromDays(1).Ticks, + Type = TaskTriggerInfo.TriggerInterval + } + }; } } } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 4d640bc95..514321e20 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -174,6 +174,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 424153f22..614c04fd2 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -1,4 +1,5 @@ -using Emby.Server.Implementations.Browser; +using System; +using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; @@ -54,6 +55,7 @@ namespace Emby.Server.Implementations.EntryPoints /// </summary> public void Dispose() { + GC.SuppressFinalize(this); } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/EntryPoints/SystemEvents.cs b/Emby.Server.Implementations/EntryPoints/SystemEvents.cs index 4ab6d32f3..08f3edb3d 100644 --- a/Emby.Server.Implementations/EntryPoints/SystemEvents.cs +++ b/Emby.Server.Implementations/EntryPoints/SystemEvents.cs @@ -23,18 +23,9 @@ namespace Emby.Server.Implementations.EntryPoints public void Run() { - _systemEvents.SessionLogoff += _systemEvents_SessionLogoff; _systemEvents.SystemShutdown += _systemEvents_SystemShutdown; } - private void _systemEvents_SessionLogoff(object sender, EventArgs e) - { - if (!_appHost.IsRunningAsService) - { - _appHost.Shutdown(); - } - } - private void _systemEvents_SystemShutdown(object sender, EventArgs e) { _appHost.Shutdown(); @@ -43,6 +34,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { _systemEvents.SystemShutdown -= _systemEvents_SystemShutdown; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index df5a7c985..d04df0d2b 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -65,6 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs index 99d39ffe0..fb9402986 100644 --- a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs @@ -130,6 +130,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index accdc5e9d..13c72bf3c 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.EntryPoints // Go up one level for indicators if (baseItem != null) { - var parent = baseItem.GetParent(); + var parent = baseItem.IsOwnedItem ? baseItem.GetOwner() : baseItem.GetParent(); if (parent != null) { @@ -160,6 +160,7 @@ namespace Emby.Server.Implementations.EntryPoints } _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index f512b723d..fe545ecff 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -823,27 +823,6 @@ namespace Emby.Server.Implementations.HttpClientManager } /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _httpClients.Clear(); - } - } - - /// <summary> /// Throws the cancellation exception. /// </summary> /// <param name="options">The options.</param> diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 8cb7b5dbf..aa679e1b9 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -160,6 +160,9 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) { Logger.Info("Transmit file {0}", Path); + + //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; + await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); return; } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index f1fea2085..86df798d0 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -7,10 +7,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; using Emby.Server.Implementations.Services; using MediaBrowser.Common.Net; @@ -24,8 +24,6 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Text; -using SocketHttpListener.Net; -using SocketHttpListener.Primitives; namespace Emby.Server.Implementations.HttpServer { @@ -34,7 +32,7 @@ namespace Emby.Server.Implementations.HttpServer private string DefaultRedirectPath { get; set; } private readonly ILogger _logger; - public IEnumerable<string> UrlPrefixes { get; private set; } + public string[] UrlPrefixes { get; private set; } private readonly List<IService> _restServices = new List<IService>(); @@ -56,14 +54,13 @@ namespace Emby.Server.Implementations.HttpServer private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; - private readonly ICertificate _certificate; + private readonly X509Certificate _certificate; private readonly IEnvironmentInfo _environment; - private readonly IStreamFactory _streamFactory; private readonly Func<Type, Func<string, object>> _funcParseFn; private readonly bool _enableDualModeSockets; - public List<Action<IRequest, IResponse, object>> RequestFilters { get; set; } - public List<Action<IRequest, IResponse, object>> ResponseFilters { get; set; } + public Action<IRequest, IResponse, object>[] RequestFilters { get; set; } + public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; } private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>(); public static HttpListenerHost Instance { get; protected set; } @@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer ILogger logger, IServerConfigurationManager config, string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) + string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, X509Certificate certificate, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) { Instance = this; @@ -87,7 +84,6 @@ namespace Emby.Server.Implementations.HttpServer _xmlSerializer = xmlSerializer; _environment = environment; _certificate = certificate; - _streamFactory = streamFactory; _funcParseFn = funcParseFn; _enableDualModeSockets = enableDualModeSockets; _fileSystem = fileSystem; @@ -95,8 +91,8 @@ namespace Emby.Server.Implementations.HttpServer _logger = logger; - RequestFilters = new List<Action<IRequest, IResponse, object>>(); - ResponseFilters = new List<Action<IRequest, IResponse, object>>(); + RequestFilters = new Action<IRequest, IResponse, object>[] { }; + ResponseFilters = new Action<IRequest, IResponse, object>[] { }; } public string GlobalResponse { get; set; } @@ -135,7 +131,9 @@ namespace Emby.Server.Implementations.HttpServer //Exec all RequestFilter attributes with Priority < 0 var attributes = GetRequestFilterAttributes(requestDto.GetType()); var i = 0; - for (; i < attributes.Length && attributes[i].Priority < 0; i++) + var count = attributes.Count; + + for (; i < count && attributes[i].Priority < 0; i++) { var attribute = attributes[i]; attribute.RequestFilter(req, res, requestDto); @@ -148,7 +146,7 @@ namespace Emby.Server.Implementations.HttpServer } //Exec remaining RequestFilter attributes with Priority >= 0 - for (; i < attributes.Length && attributes[i].Priority >= 0; i++) + for (; i < count && attributes[i].Priority >= 0; i++) { var attribute = attributes[i]; attribute.RequestFilter(req, res, requestDto); @@ -167,7 +165,7 @@ namespace Emby.Server.Implementations.HttpServer ServiceOperationsMap[requestType] = serviceType; } - private IHasRequestFilter[] GetRequestFilterAttributes(Type requestDtoType) + private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType) { var attributes = requestDtoType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>().ToList(); @@ -179,40 +177,13 @@ namespace Emby.Server.Implementations.HttpServer attributes.Sort((x, y) => x.Priority - y.Priority); - return attributes.ToArray(attributes.Count); - } - - /// <summary> - /// Starts the Web Service - /// </summary> - private void StartListener() - { - WebSocketSharpRequest.HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes.First()); - - _listener = GetListener(); - - _listener.WebSocketConnected = OnWebSocketConnected; - _listener.WebSocketConnecting = OnWebSocketConnecting; - _listener.ErrorHandler = ErrorHandler; - _listener.RequestHandler = RequestHandler; - - _listener.Start(UrlPrefixes); - } - - public static string GetHandlerPathIfAny(string listenerUrl) - { - if (listenerUrl == null) return null; - var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); - if (pos == -1) return null; - var startHostUrl = listenerUrl.Substring(pos + "://".Length); - var endPos = startHostUrl.IndexOf('/'); - if (endPos == -1) return null; - var endHostUrl = startHostUrl.Substring(endPos + 1); - return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); + return attributes; } private IHttpListener GetListener() { + //return new KestrelHost.KestrelListener(_logger, _environment, _fileSystem); + return new WebSocketSharpListener(_logger, _certificate, _memoryStreamProvider, @@ -220,22 +191,11 @@ namespace Emby.Server.Implementations.HttpServer _networkManager, _socketFactory, _cryptoProvider, - _streamFactory, _enableDualModeSockets, - GetRequest, _fileSystem, _environment); } - private IHttpRequest GetRequest(HttpListenerContext httpContext) - { - var operationName = httpContext.Request.GetOperationName(); - - var req = new WebSocketSharpRequest(httpContext, operationName, _logger, _memoryStreamProvider); - - return req; - } - private void OnWebSocketConnecting(WebSocketConnectingEventArgs args) { if (_disposed) @@ -348,7 +308,8 @@ namespace Emby.Server.Implementations.HttpServer if (_listener != null) { _logger.Info("Stopping HttpListener..."); - _listener.Stop(); + var task = _listener.Stop(); + Task.WaitAll(task); _logger.Info("HttpListener stopped"); } } @@ -434,7 +395,7 @@ namespace Emby.Server.Implementations.HttpServer return address.Trim('/'); } - private bool ValidateHost(Uri url) + private bool ValidateHost(string host) { var hosts = _config .Configuration @@ -447,7 +408,7 @@ namespace Emby.Server.Implementations.HttpServer return true; } - var host = url.Host ?? string.Empty; + host = host ?? string.Empty; _logger.Debug("Validating host {0}", host); @@ -465,7 +426,7 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Overridable method that can be used to implement a custom hnandler /// </summary> - protected async Task RequestHandler(IHttpRequest httpReq, Uri url, CancellationToken cancellationToken) + protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { var date = DateTime.Now; var httpRes = httpReq.Response; @@ -484,7 +445,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (!ValidateHost(url)) + if (!ValidateHost(host)) { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; @@ -504,9 +465,7 @@ namespace Emby.Server.Implementations.HttpServer } var operationName = httpReq.OperationName; - var localPath = url.LocalPath; - var urlString = url.OriginalString; enableLog = EnableLogging(urlString, localPath); urlToLog = urlString; logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1; @@ -698,13 +657,18 @@ namespace Emby.Server.Implementations.HttpServer ServiceController.Init(this, types); - var requestFilters = _appHost.GetExports<IRequestFilter>().ToList(); - foreach (var filter in requestFilters) + var list = new List<Action<IRequest, IResponse, object>>(); + foreach (var filter in _appHost.GetExports<IRequestFilter>()) { - RequestFilters.Add(filter.Filter); + list.Add(filter.Filter); } - ResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); + RequestFilters = list.ToArray(); + + ResponseFilters = new Action<IRequest, IResponse, object>[] + { + new ResponseFilter(_logger).FilterResponse + }; } public RouteAttribute[] GetRouteAttributes(Type requestType) @@ -721,19 +685,19 @@ namespace Emby.Server.Implementations.HttpServer Summary = route.Summary }); - routes.Add(new RouteAttribute(NormalizeRoutePath(route.Path), route.Verbs) + routes.Add(new RouteAttribute(NormalizeMediaBrowserRoutePath(route.Path), route.Verbs) { Notes = route.Notes, Priority = route.Priority, Summary = route.Summary }); - routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs) - { - Notes = route.Notes, - Priority = route.Priority, - Summary = route.Summary - }); + //routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs) + //{ + // Notes = route.Notes, + // Priority = route.Priority, + // Summary = route.Summary + //}); } return routes.ToArray(routes.Count); @@ -774,24 +738,24 @@ namespace Emby.Server.Implementations.HttpServer return "emby/" + path; } - private string DoubleNormalizeEmbyRoutePath(string path) + private string NormalizeMediaBrowserRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { - return "/emby/emby" + path; + return "/mediabrowser" + path; } - return "emby/emby/" + path; + return "mediabrowser/" + path; } - private string NormalizeRoutePath(string path) + private string DoubleNormalizeEmbyRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { - return "/mediabrowser" + path; + return "/emby/emby" + path; } - return "mediabrowser/" + path; + return "emby/emby/" + path; } private bool _disposed; @@ -819,10 +783,18 @@ namespace Emby.Server.Implementations.HttpServer GC.SuppressFinalize(this); } - public void StartServer(IEnumerable<string> urlPrefixes) + public void StartServer(string[] urlPrefixes) { - UrlPrefixes = urlPrefixes.ToList(); - StartListener(); + UrlPrefixes = urlPrefixes; + + _listener = GetListener(); + + _listener.WebSocketConnected = OnWebSocketConnected; + _listener.WebSocketConnecting = OnWebSocketConnecting; + _listener.ErrorHandler = ErrorHandler; + _listener.RequestHandler = RequestHandler; + + _listener.Start(UrlPrefixes); } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 7bd8fe2bf..f5a1fe246 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -424,11 +424,14 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - // Quotes are valid in linux. They'll possibly cause issues here - var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); - if (!string.IsNullOrWhiteSpace(filename)) + if (!options.ResponseHeaders.ContainsKey("Content-Disposition")) { - options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\""; + // Quotes are valid in linux. They'll possibly cause issues here + var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); + if (!string.IsNullOrWhiteSpace(filename)) + { + options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\""; + } } return GetStaticResult(requestContext, options); @@ -487,69 +490,8 @@ namespace Emby.Server.Implementations.HttpServer return result; } - var compress = ShouldCompressResponse(requestContext, contentType); - var hasHeaders = await GetStaticResult(requestContext, options, compress).ConfigureAwait(false); - AddResponseHeaders(hasHeaders, options.ResponseHeaders); - - return hasHeaders; - } - - /// <summary> - /// Shoulds the compress response. - /// </summary> - /// <param name="requestContext">The request context.</param> - /// <param name="contentType">Type of the content.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool ShouldCompressResponse(IRequest requestContext, string contentType) - { - // It will take some work to support compression with byte range requests - if (!string.IsNullOrWhiteSpace(requestContext.Headers.Get("Range"))) - { - return false; - } - - // Don't compress media - if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Don't compress images - if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(contentType, "application/x-javascript", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (string.Equals(contentType, "application/xml", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - return false; - } - - return true; - } - - /// <summary> - /// The us culture - /// </summary> - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private async Task<IHasHeaders> GetStaticResult(IRequest requestContext, StaticResultOptions options, bool compress) - { var isHeadRequest = options.IsHeadRequest; var factoryFn = options.ContentFactory; - var contentType = options.ContentType; var responseHeaders = options.ResponseHeaders; //var requestedCompressionType = GetCompressionType(requestContext); @@ -558,22 +500,28 @@ namespace Emby.Server.Implementations.HttpServer if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) { - return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) + var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) { OnComplete = options.OnComplete, OnError = options.OnError, FileShare = options.FileShare }; + + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; } if (!string.IsNullOrWhiteSpace(rangeHeader)) { var stream = await factoryFn().ConfigureAwait(false); - return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) + var hasHeaders = new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) { OnComplete = options.OnComplete }; + + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; } else { @@ -588,15 +536,23 @@ namespace Emby.Server.Implementations.HttpServer return GetHttpResult(new byte[] { }, contentType, true); } - return new StreamWriter(stream, contentType, _logger) + var hasHeaders = new StreamWriter(stream, contentType, _logger) { OnComplete = options.OnComplete, OnError = options.OnError }; + + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; } } /// <summary> + /// The us culture + /// </summary> + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// <summary> /// Adds the caching responseHeaders. /// </summary> private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, bool noCache) diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 82175dbed..9feb2311d 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer /// Gets or sets the request handler. /// </summary> /// <value>The request handler.</value> - Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; } + Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; } /// <summary> /// Gets or sets the web socket handler. @@ -42,6 +42,6 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Stops this instance. /// </summary> - void Stop(); + Task Stop(); } } diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs index de30dc30a..46bb4c7f9 100644 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs @@ -1,10 +1,8 @@ using MediaBrowser.Model.Logging; using System; using System.Globalization; -using System.Linq; using MediaBrowser.Model.Services; using SocketHttpListener.Net; -using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.HttpServer { @@ -30,7 +28,20 @@ namespace Emby.Server.Implementations.HttpServer } else { - var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray(headers.Count)); + var headerText = string.Empty; + var index = 0; + + foreach (var i in headers) + { + if (index > 0) + { + headerText += ", "; + } + + headerText += i.Name + "=" + i.Value; + + index++; + } logger.Info("HTTP {0} {1}. {2}", method, url, headerText); } @@ -49,7 +60,8 @@ namespace Emby.Server.Implementations.HttpServer var durationMs = duration.TotalMilliseconds; var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; - var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); + //var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); + var headerText = string.Empty; logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText); } } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 4d00c9b19..fadab4482 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -7,8 +7,8 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using System; -using System.Collections.Generic; using System.Linq; +using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.HttpServer.Security { @@ -38,19 +38,19 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> public string HtmlRedirect { get; set; } - public void Authenticate(IServiceRequest request, + public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues) { ValidateUser(request, authAttribtues); } - private void ValidateUser(IServiceRequest request, + private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(request); - if (!IsExemptFromAuthenticationToken(auth, authAttribtues)) + if (!IsExemptFromAuthenticationToken(auth, authAttribtues, request)) { var valid = IsValidConnectKey(auth.Token); @@ -76,9 +76,9 @@ namespace Emby.Server.Implementations.HttpServer.Security var info = GetTokenInfo(request); - if (!IsExemptFromRoles(auth, authAttribtues, info)) + if (!IsExemptFromRoles(auth, authAttribtues, request, info)) { - var roles = authAttribtues.GetRoles().ToList(); + var roles = authAttribtues.GetRoles(); ValidateRoles(roles, user); } @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private void ValidateUserAccess(User user, IServiceRequest request, + private void ValidateUserAccess(User user, IRequest request, IAuthenticationAttributes authAttribtues, AuthorizationInfo auth) { @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.HttpServer.Security !authAttribtues.EscapeParentalControl && !user.IsParentalScheduleAllowed()) { - request.AddResponseHeader("X-Application-Error-Code", "ParentalControl"); + request.Response.AddHeader("X-Application-Error-Code", "ParentalControl"); throw new SecurityException("This user account is not allowed access at this time.") { @@ -132,23 +132,33 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues) + private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request) { if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard) { return true; } + if (authAttribtues.AllowLocal && request.IsLocal) + { + return true; + } + return false; } - private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo) + private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request, AuthenticationInfo tokenInfo) { if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard) { return true; } + if (authAttribtues.AllowLocal && request.IsLocal) + { + return true; + } + if (string.IsNullOrWhiteSpace(auth.Token)) { return true; @@ -162,7 +172,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return false; } - private void ValidateRoles(List<string> roles, User user) + private void ValidateRoles(string[] roles, User user) { if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase)) { @@ -196,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private AuthenticationInfo GetTokenInfo(IServiceRequest request) + private AuthenticationInfo GetTokenInfo(IRequest request) { object info; request.Items.TryGetValue("OriginalAuthenticationInfo", out info); @@ -213,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return ConnectManager.IsAuthorizationTokenValid(token); } - private void ValidateSecurityToken(IServiceRequest request, string token) + private void ValidateSecurityToken(IRequest request, string token) { if (string.IsNullOrWhiteSpace(token)) { diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index ede85fb67..c9d5ed007 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -3,8 +3,8 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using System; using System.Collections.Generic; -using System.Linq; using MediaBrowser.Model.Services; +using System.Linq; namespace Emby.Server.Implementations.HttpServer.Security { @@ -21,11 +21,10 @@ namespace Emby.Server.Implementations.HttpServer.Security public AuthorizationInfo GetAuthorizationInfo(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetAuthorizationInfo(req); + return GetAuthorizationInfo((IRequest)requestContext); } - public AuthorizationInfo GetAuthorizationInfo(IServiceRequest requestContext) + public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext) { object cached; if (requestContext.Items.TryGetValue("AuthorizationInfo", out cached)) @@ -41,7 +40,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private AuthorizationInfo GetAuthorization(IServiceRequest httpReq) + private AuthorizationInfo GetAuthorization(IRequest httpReq) { var auth = GetAuthorizationDictionary(httpReq); @@ -90,7 +89,7 @@ namespace Emby.Server.Implementations.HttpServer.Security AccessToken = token }); - var tokenInfo = result.Items.FirstOrDefault(); + var tokenInfo = result.Items.Length > 0 ? result.Items[0] : null; if (tokenInfo != null) { @@ -135,7 +134,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(IServiceRequest httpReq) + private Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -161,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // There should be at least to parts if (parts.Length != 2) return null; - var acceptedNames = new[] { "MediaBrowser", "Emby"}; + var acceptedNames = new[] { "MediaBrowser", "Emby" }; // It has to be a digest request if (!acceptedNames.Contains(parts[0] ?? string.Empty, StringComparer.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 33dd4e2d7..dd5d64bf6 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public Task<SessionInfo> GetSession(IServiceRequest requestContext) + public Task<SessionInfo> GetSession(IRequest requestContext) { var authorization = _authContext.GetAuthorizationInfo(requestContext); @@ -38,7 +38,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.RemoteIp, user); } - private AuthenticationInfo GetTokenInfo(IServiceRequest request) + private AuthenticationInfo GetTokenInfo(IRequest request) { object info; request.Items.TryGetValue("OriginalAuthenticationInfo", out info); @@ -47,11 +47,10 @@ namespace Emby.Server.Implementations.HttpServer.Security public Task<SessionInfo> GetSession(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetSession(req); + return GetSession((IRequest)requestContext); } - public async Task<User> GetUser(IServiceRequest requestContext) + public async Task<User> GetUser(IRequest requestContext) { var session = await GetSession(requestContext).ConfigureAwait(false); @@ -60,8 +59,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public Task<User> GetUser(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetUser(req); + return GetUser((IRequest)requestContext); } } } diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs index 9823a2ff5..cc7a4557e 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs @@ -123,6 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index e648838b2..8fb0d4f3e 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.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; @@ -22,22 +23,20 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private HttpListener _listener; private readonly ILogger _logger; - private readonly ICertificate _certificate; + private readonly X509Certificate _certificate; private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly ITextEncoding _textEncoding; private readonly INetworkManager _networkManager; private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; - private readonly IStreamFactory _streamFactory; private readonly IFileSystem _fileSystem; - private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory; 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) + public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment) { _logger = logger; _certificate = certificate; @@ -46,9 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _networkManager = networkManager; _socketFactory = socketFactory; _cryptoProvider = cryptoProvider; - _streamFactory = streamFactory; _enableDualMode = enableDualMode; - _httpRequestFactory = httpRequestFactory; _fileSystem = fileSystem; _environment = environment; @@ -56,7 +53,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } public Action<Exception, IRequest, bool> ErrorHandler { get; set; } - public Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; } + public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; } public Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; } @@ -65,7 +62,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Start(IEnumerable<string> urlPrefixes) { if (_listener == null) - _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment); + _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment); _listener.EnableDualMode = _enableDualMode; @@ -87,8 +84,8 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private void ProcessContext(HttpListenerContext context) { - InitTask(context, _disposeCancellationToken); - //Task.Run(() => InitTask(context, _disposeCancellationToken)); + //InitTask(context, _disposeCancellationToken); + Task.Run(() => InitTask(context, _disposeCancellationToken)); } private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) @@ -117,7 +114,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return Task.FromResult(true); } - return RequestHandler(httpReq, request.Url, cancellationToken); + var uri = request.Url; + + return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); } private void ProcessWebSocketRequest(HttpListenerContext ctx) @@ -173,22 +172,23 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private IHttpRequest GetRequest(HttpListenerContext httpContext) { - return _httpRequestFactory(httpContext); + var operationName = httpContext.Request.GetOperationName(); + + var req = new WebSocketSharpRequest(httpContext, operationName, _logger, _memoryStreamProvider); + + return req; } - public void Stop() + public Task Stop() { _disposeCancellationTokenSource.Cancel(); if (_listener != null) { - foreach (var prefix in _listener.Prefixes.ToList()) - { - _listener.Prefixes.Remove(prefix); - } - _listener.Close(); } + + return Task.FromResult(true); } public void Dispose() diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index 2dfe6a9e3..522377f0c 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -27,6 +27,20 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _memoryStreamProvider = memoryStreamProvider; this.request = httpContext.Request; this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); + + //HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); + } + + private static string GetHandlerPathIfAny(string listenerUrl) + { + if (listenerUrl == null) return null; + var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); + if (pos == -1) return null; + var startHostUrl = listenerUrl.Substring(pos + "://".Length); + var endPos = startHostUrl.IndexOf('/'); + if (endPos == -1) return null; + var endHostUrl = startHostUrl.Substring(endPos + 1); + return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); } public HttpListenerRequest HttpRequest @@ -108,7 +122,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return remoteIp ?? (remoteIp = (CheckBadChars(XForwardedFor)) ?? (NormalizeIp(CheckBadChars(XRealIp)) ?? - (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.IpAddress.ToString()) : null))); + (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); } } @@ -232,13 +246,12 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp set { this.responseContentType = value; - HasExplicitResponseContentType = true; } } public const string FormUrlEncoded = "application/x-www-form-urlencoded"; public const string MultiPartFormData = "multipart/form-data"; - private static string GetResponseContentType(IRequest httpReq) + public static string GetResponseContentType(IRequest httpReq) { var specifiedContentType = GetQueryStringContentType(httpReq); if (!string.IsNullOrEmpty(specifiedContentType)) return specifiedContentType; @@ -346,8 +359,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp : strVal.Substring(0, pos); } - public bool HasExplicitResponseContentType { get; private set; } - public static string HandlerFactoryPath; private string pathInfo; @@ -490,13 +501,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp get { return HttpMethod; } } - public string Param(string name) - { - return Headers[name] - ?? QueryString[name] - ?? FormData[name]; - } - public string ContentType { get { return request.ContentType; } @@ -584,18 +588,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return stream; } - public static string GetHandlerPathIfAny(string listenerUrl) - { - if (listenerUrl == null) return null; - var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); - if (pos == -1) return null; - var startHostUrl = listenerUrl.Substring(pos + "://".Length); - var endPos = startHostUrl.IndexOf('/'); - if (endPos == -1) return null; - var endHostUrl = startHostUrl.Substring(endPos + 1); - return String.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); - } - public static string NormalizePathInfo(string pathInfo, string handlerPath) { if (handlerPath != null && pathInfo.TrimStart('/').StartsWith( diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index d6762d94b..5b51c0cf1 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -29,7 +29,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } public IRequest Request { get; private set; } - public bool UseBufferedStream { get; set; } public Dictionary<string, object> Items { get; private set; } public object OriginalResponse { diff --git a/Emby.Server.Implementations/HttpServerFactory.cs b/Emby.Server.Implementations/HttpServerFactory.cs index 007f5c829..717c50e7b 100644 --- a/Emby.Server.Implementations/HttpServerFactory.cs +++ b/Emby.Server.Implementations/HttpServerFactory.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations IJsonSerializer json, IXmlSerializer xml, IEnvironmentInfo environment, - ICertificate certificate, + X509Certificate certificate, IFileSystem fileSystem, bool enableDualModeSockets) { @@ -63,7 +63,6 @@ namespace Emby.Server.Implementations xml, environment, certificate, - new StreamFactory(), GetParseFn, enableDualModeSockets, fileSystem); @@ -74,37 +73,4 @@ namespace Emby.Server.Implementations return s => JsvReader.GetParseFn(propertyType)(s); } } - - public class StreamFactory : IStreamFactory - { - public Stream CreateNetworkStream(IAcceptSocket acceptSocket, bool ownsSocket) - { - var netSocket = (NetAcceptSocket)acceptSocket; - - return new SocketStream(netSocket.Socket, ownsSocket); - } - - public Task AuthenticateSslStreamAsServer(Stream stream, ICertificate certificate) - { - var sslStream = (SslStream)stream; - var cert = (Certificate)certificate; - - return sslStream.AuthenticateAsServerAsync(cert.X509Certificate); - } - - public Stream CreateSslStream(Stream innerStream, bool leaveInnerStreamOpen) - { - return new SslStream(innerStream, leaveInnerStreamOpen); - } - } - - public class Certificate : ICertificate - { - public Certificate(X509Certificate x509Certificate) - { - X509Certificate = x509Certificate; - } - - public X509Certificate X509Certificate { get; private set; } - } } diff --git a/Emby.Server.Implementations/IO/AsyncStreamCopier.cs b/Emby.Server.Implementations/IO/AsyncStreamCopier.cs deleted file mode 100644 index 9e5ce0604..000000000 --- a/Emby.Server.Implementations/IO/AsyncStreamCopier.cs +++ /dev/null @@ -1,459 +0,0 @@ -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 0ec62d895..315bee103 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -209,7 +209,7 @@ namespace Emby.Server.Implementations.IO // If the item has been deleted find the first valid parent that still exists while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) { - item = item.GetParent(); + item = item.IsOwnedItem ? item.GetOwner() : item.GetParent(); if (item == null) { @@ -238,6 +238,7 @@ namespace Emby.Server.Implementations.IO { _disposed = true; DisposeTimer(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/IO/IsoManager.cs b/Emby.Server.Implementations/IO/IsoManager.cs index 903d5f301..dc0b9e122 100644 --- a/Emby.Server.Implementations/IO/IsoManager.cs +++ b/Emby.Server.Implementations/IO/IsoManager.cs @@ -70,6 +70,7 @@ namespace Emby.Server.Implementations.IO { mounter.Dispose(); } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 3994e2b00..56b10a7e6 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -649,6 +649,7 @@ namespace Emby.Server.Implementations.IO public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 57e42985d..eab52e5e8 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -14,10 +14,10 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; -using MediaBrowser.Naming.Audio; -using MediaBrowser.Naming.Common; -using MediaBrowser.Naming.TV; -using MediaBrowser.Naming.Video; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Emby.Naming.TV; +using Emby.Naming.Video; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -38,7 +38,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Library; using MediaBrowser.Model.Net; using SortOrder = MediaBrowser.Model.Entities.SortOrder; -using VideoResolver = MediaBrowser.Naming.Video.VideoResolver; +using VideoResolver = Emby.Naming.Video.VideoResolver; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dto; @@ -386,7 +386,7 @@ namespace Emby.Server.Implementations.Library item.Id); } - var parent = item.Parent; + var parent = item.IsOwnedItem ? item.GetOwner() : item.GetParent(); var locationType = item.LocationType; @@ -453,12 +453,28 @@ namespace Emby.Server.Implementations.Library if (parent != null) { - await parent.ValidateChildren(new SimpleProgress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false).ConfigureAwait(false); + var parentFolder = parent as Folder; + if (parentFolder != null) + { + await parentFolder.ValidateChildren(new SimpleProgress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false).ConfigureAwait(false); + } + else + { + await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false); + } } } else if (parent != null) { - parent.RemoveChild(item); + var parentFolder = parent as Folder; + if (parentFolder != null) + { + parentFolder.RemoveChild(item); + } + else + { + await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false); + } } ItemRepository.DeleteItem(item.Id, CancellationToken.None); @@ -620,37 +636,12 @@ namespace Emby.Server.Implementations.Library return ResolveItem(args, resolvers); } - private readonly List<string> _ignoredPaths = new List<string>(); - - public void RegisterIgnoredPath(string path) - { - lock (_ignoredPaths) - { - _ignoredPaths.Add(path); - } - } - public void UnRegisterIgnoredPath(string path) - { - lock (_ignoredPaths) - { - _ignoredPaths.Remove(path); - } - } - public bool IgnoreFile(FileSystemMetadata file, BaseItem parent) { if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent))) { return true; } - - //lock (_ignoredPaths) - { - if (_ignoredPaths.Contains(file.FullName, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - } return false; } @@ -846,8 +837,7 @@ namespace Emby.Server.Implementations.Library { Path = path, IsFolder = isFolder, - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), Limit = 1, DtoOptions = new DtoOptions(true) }; @@ -1777,6 +1767,37 @@ namespace Emby.Server.Implementations.Library return orderedItems ?? items; } + public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<Tuple<string, SortOrder>> orderByList) + { + var isFirst = true; + + IOrderedEnumerable<BaseItem> orderedItems = null; + + foreach (var orderBy in orderByList) + { + var comparer = GetComparer(orderBy.Item1, user); + if (comparer == null) + { + continue; + } + + var sortOrder = orderBy.Item2; + + if (isFirst) + { + orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer); + } + else + { + orderedItems = sortOrder == SortOrder.Descending ? orderedItems.ThenByDescending(i => i, comparer) : orderedItems.ThenBy(i => i, comparer); + } + + isFirst = false; + } + + return orderedItems ?? items; + } + /// <summary> /// Gets the comparer. /// </summary> @@ -2341,7 +2362,7 @@ namespace Emby.Server.Implementations.Library public bool IsVideoFile(string path, LibraryOptions libraryOptions) { - var resolver = new VideoResolver(GetNamingOptions(), new NullLogger()); + var resolver = new VideoResolver(GetNamingOptions()); return resolver.IsVideoFile(path); } @@ -2368,8 +2389,7 @@ namespace Emby.Server.Implementations.Library public bool FillMissingEpisodeNumbersFromPath(Episode episode) { - var resolver = new EpisodeResolver(GetNamingOptions(), - new NullLogger()); + var resolver = new EpisodeResolver(GetNamingOptions()); var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; @@ -2377,11 +2397,11 @@ namespace Emby.Server.Implementations.Library var episodeInfo = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? resolver.Resolve(episode.Path, isFolder) : - new MediaBrowser.Naming.TV.EpisodeInfo(); + new Emby.Naming.TV.EpisodeInfo(); if (episodeInfo == null) { - episodeInfo = new MediaBrowser.Naming.TV.EpisodeInfo(); + episodeInfo = new Emby.Naming.TV.EpisodeInfo(); } var changed = false; @@ -2552,7 +2572,7 @@ namespace Emby.Server.Implementations.Library public ItemLookupInfo ParseName(string name) { - var resolver = new VideoResolver(GetNamingOptions(), new NullLogger()); + var resolver = new VideoResolver(GetNamingOptions()); var result = resolver.CleanDateTime(name); var cleanName = resolver.CleanString(result.Name); @@ -2573,7 +2593,7 @@ namespace Emby.Server.Implementations.Library .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); - var videoListResolver = new VideoListResolver(namingOptions, new NullLogger()); + var videoListResolver = new VideoListResolver(namingOptions); var videos = videoListResolver.Resolve(fileSystemChildren); @@ -2600,8 +2620,11 @@ namespace Emby.Server.Implementations.Library { video = dbItem; } - - video.ExtraType = ExtraType.Trailer; + else + { + // item is new + video.ExtraType = ExtraType.Trailer; + } video.TrailerTypes = new List<TrailerType> { TrailerType.LocalTrailer }; return video; @@ -2621,7 +2644,7 @@ namespace Emby.Server.Implementations.Library .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); - var videoListResolver = new VideoListResolver(namingOptions, new NullLogger()); + var videoListResolver = new VideoListResolver(namingOptions); var videos = videoListResolver.Resolve(fileSystemChildren); @@ -2747,7 +2770,7 @@ namespace Emby.Server.Implementations.Library private void SetExtraTypeFromFilename(Video item) { - var resolver = new ExtraResolver(GetNamingOptions(), new NullLogger(), new RegexProvider()); + var resolver = new ExtraResolver(GetNamingOptions(), new RegexProvider()); var result = resolver.GetExtraInfo(item.Path); @@ -2842,13 +2865,6 @@ namespace Emby.Server.Implementations.Library await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false); - var newImage = item.GetImageInfo(image.Type, imageIndex); - - if (newImage != null) - { - newImage.IsPlaceholder = image.IsPlaceholder; - } - await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); return item.GetImageInfo(image.Type, imageIndex); diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 6e0489d88..d60a04353 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -524,6 +524,7 @@ namespace Emby.Server.Implementations.Library public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } private readonly object _disposeLock = new object(); diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 7f77170ad..1cbf4235a 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Library @@ -88,7 +89,7 @@ namespace Emby.Server.Implementations.Library Limit = 200, - SortBy = new[] { ItemSortBy.Random }, + OrderBy = new [] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, DtoOptions = dtoOptions diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 70fe8d9ef..b8ec41805 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -4,7 +4,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Naming.Audio; +using Emby.Naming.Audio; using System; using System.Collections.Generic; using System.IO; @@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); - var parser = new AlbumParser(namingOptions, new NullLogger()); + var parser = new AlbumParser(namingOptions); var result = parser.ParseMultiPart(path); return result.IsMultiPart; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index fa4f026f4..e3200a099 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -1,7 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; -using MediaBrowser.Naming.Video; +using Emby.Naming.Video; using System; using System.IO; using System.Linq; @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library.Resolvers var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); // If the path is a file check for a matching extensions - var parser = new MediaBrowser.Naming.Video.VideoResolver(namingOptions, new NullLogger()); + var parser = new Emby.Naming.Video.VideoResolver(namingOptions); if (args.IsDirectory) { @@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Library.Resolvers { var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); - var resolver = new Format3DParser(namingOptions, new NullLogger()); + var resolver = new Format3DParser(namingOptions); var result = resolver.Parse(video.Path); Set3DFormat(video, result.Is3D, result.Format3D); diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 1e5c0beed..cd1264754 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -6,7 +6,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; -using MediaBrowser.Naming.Video; +using Emby.Naming.Video; using System; using System.Collections.Generic; using System.IO; @@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); - var resolver = new VideoListResolver(namingOptions, new NullLogger()); + var resolver = new VideoListResolver(namingOptions); var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList(); var result = new MultiItemResolverResult @@ -490,7 +490,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); - var resolver = new StackResolver(namingOptions, new NullLogger()); + var resolver = new StackResolver(namingOptions); var result = resolver.ResolveDirectories(folderPaths); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 830bd9d85..bbe1bba85 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -3,8 +3,8 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Globalization; -using MediaBrowser.Naming.Common; -using MediaBrowser.Naming.TV; +using Emby.Naming.Common; +using Emby.Naming.TV; namespace Emby.Server.Implementations.Library.Resolvers.TV { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index e1c18c913..a693e3b26 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -4,8 +4,8 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Naming.Common; -using MediaBrowser.Naming.TV; +using Emby.Naming.Common; +using Emby.Naming.TV; using System; using System.Collections.Generic; using System.IO; @@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var allowOptimisticEpisodeDetection = isTvContentType; var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(allowOptimisticEpisodeDetection); - var episodeResolver = new MediaBrowser.Naming.TV.EpisodeResolver(namingOptions, new NullLogger()); + var episodeResolver = new Emby.Naming.TV.EpisodeResolver(namingOptions); var episodeInfo = episodeResolver.Resolve(fullName, false, false); if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue) { @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV } } - logger.Debug("{0} is not a series folder.", path); + //logger.Debug("{0} is not a series folder.", path); return false; } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index d4c4f2794..b1ed034ca 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Extensions; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Library @@ -169,7 +170,7 @@ namespace Emby.Server.Implementations.Library Limit = query.Limit, IncludeItemsByName = string.IsNullOrWhiteSpace(query.ParentId), ParentId = string.IsNullOrWhiteSpace(query.ParentId) ? (Guid?)null : new Guid(query.ParentId), - SortBy = new[] { ItemSortBy.SortName }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }, Recursive = true, IsKids = query.IsKids, diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index e5fe2969f..0f48ff46b 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -180,11 +180,6 @@ namespace Emby.Server.Implementations.Library } } - public Task<User> AuthenticateUser(string username, string passwordSha1, string remoteEndPoint) - { - return AuthenticateUser(username, passwordSha1, null, remoteEndPoint); - } - public bool IsValidUsername(string username) { // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) @@ -223,7 +218,7 @@ namespace Emby.Server.Implementations.Library return builder.ToString(); } - public async Task<User> AuthenticateUser(string username, string passwordSha1, string passwordMd5, string remoteEndPoint) + public async Task<User> AuthenticateUser(string username, string password, string hashedPassword, string passwordMd5, string remoteEndPoint) { if (string.IsNullOrWhiteSpace(username)) { @@ -237,23 +232,23 @@ namespace Emby.Server.Implementations.Library if (user != null) { + if (password != null) + { + hashedPassword = GetHashedString(user, password); + } + // Authenticate using local credentials if not a guest if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value != UserLinkType.Guest) { - success = string.Equals(GetPasswordHash(user), passwordSha1.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) - { - success = string.Equals(GetLocalPasswordHash(user), passwordSha1.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } + success = AuthenticateLocalUser(user, password, hashedPassword, remoteEndPoint); } // Maybe user accidently entered connect credentials. let's be flexible - if (!success && user.ConnectLinkType.HasValue && !string.IsNullOrWhiteSpace(passwordMd5) && !string.IsNullOrWhiteSpace(user.ConnectUserName)) + if (!success && user.ConnectLinkType.HasValue && !string.IsNullOrWhiteSpace(user.ConnectUserName)) { try { - await _connectFactory().Authenticate(user.ConnectUserName, passwordMd5).ConfigureAwait(false); + await _connectFactory().Authenticate(user.ConnectUserName, password, passwordMd5).ConfigureAwait(false); success = true; } catch @@ -268,7 +263,7 @@ namespace Emby.Server.Implementations.Library { try { - var connectAuthResult = await _connectFactory().Authenticate(username, passwordMd5).ConfigureAwait(false); + var connectAuthResult = await _connectFactory().Authenticate(username, password, passwordMd5).ConfigureAwait(false); user = Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectAuthResult.User.Id, StringComparison.OrdinalIgnoreCase)); @@ -307,6 +302,36 @@ namespace Emby.Server.Implementations.Library return success ? user : null; } + private bool AuthenticateLocalUser(User user, string password, string hashedPassword, string remoteEndPoint) + { + bool success; + + if (password == null) + { + // legacy + success = string.Equals(GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + success = string.Equals(GetPasswordHash(user), GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + } + + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + { + if (password == null) + { + // legacy + success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + success = string.Equals(GetLocalPasswordHash(user), GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + } + } + + return success; + } + private void UpdateInvalidLoginAttemptCount(User user, int newValue) { if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0) @@ -342,29 +367,39 @@ namespace Emby.Server.Implementations.Library private string GetPasswordHash(User user) { return string.IsNullOrEmpty(user.Password) - ? GetSha1String(string.Empty) + ? GetEmptyHashedString(user) : user.Password; } private string GetLocalPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) - ? GetSha1String(string.Empty) + ? GetEmptyHashedString(user) : user.EasyPassword; } - private bool IsPasswordEmpty(string passwordHash) + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + } + + private string GetEmptyHashedString(User user) { - return string.Equals(passwordHash, GetSha1String(string.Empty), StringComparison.OrdinalIgnoreCase); + return GetHashedString(user, string.Empty); } /// <summary> - /// Gets the sha1 string. + /// Gets the hashed string. /// </summary> - /// <param name="str">The STR.</param> - /// <returns>System.String.</returns> - private string GetSha1String(string str) + private string GetHashedString(User user, string str) { + var salt = user.Salt; + if (salt != null) + { + // return BCrypt.HashPassword(str, salt); + } + + // legacy return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); } @@ -407,8 +442,8 @@ namespace Emby.Server.Implementations.Library var passwordHash = GetPasswordHash(user); - var hasConfiguredPassword = !IsPasswordEmpty(passwordHash); - var hasConfiguredEasyPassword = !IsPasswordEmpty(GetLocalPasswordHash(user)); + var hasConfiguredPassword = !IsPasswordEmpty(user, passwordHash); + var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : @@ -460,14 +495,6 @@ namespace Emby.Server.Implementations.Library { var dto = GetUserDto(user); - var offlinePasswordHash = GetLocalPasswordHash(user); - dto.HasPassword = !IsPasswordEmpty(offlinePasswordHash); - - dto.OfflinePasswordSalt = Guid.NewGuid().ToString("N"); - - // Hash the pin with the device Id to create a unique result for this device - dto.OfflinePassword = GetSha1String((offlinePasswordHash + dto.OfflinePasswordSalt).ToLower()); - dto.ServerName = _appHost.FriendlyName; return dto; @@ -491,11 +518,12 @@ namespace Emby.Server.Implementations.Library /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - public Task RefreshUsersMetadata(CancellationToken cancellationToken) + public async Task RefreshUsersMetadata(CancellationToken cancellationToken) { - var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken)).ToList(); - - return Task.WhenAll(tasks); + foreach (var user in Users) + { + await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken).ConfigureAwait(false); + } } /// <summary> @@ -666,8 +694,7 @@ namespace Emby.Server.Implementations.Library DeleteUserPolicy(user); - // Force this to be lazy loaded again - Users = LoadUsers(); + Users = allUsers.Where(i => i.Id != user.Id).ToList(); OnUserDeleted(user); } @@ -683,23 +710,29 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> public void ResetPassword(User user) { - ChangePassword(user, GetSha1String(string.Empty)); + ChangePassword(user, string.Empty, null); } public void ResetEasyPassword(User user) { - ChangeEasyPassword(user, GetSha1String(string.Empty)); + ChangeEasyPassword(user, string.Empty, null); } - public void ChangePassword(User user, string newPasswordSha1) + public void ChangePassword(User user, string newPassword, string newPasswordHash) { if (user == null) { throw new ArgumentNullException("user"); } - if (string.IsNullOrWhiteSpace(newPasswordSha1)) + + if (newPassword != null) { - throw new ArgumentNullException("newPasswordSha1"); + newPasswordHash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException("newPasswordHash"); } if (user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest) @@ -707,25 +740,31 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Passwords for guests cannot be changed."); } - user.Password = newPasswordSha1; + user.Password = newPasswordHash; UpdateUser(user); EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs<User>(user), _logger); } - public void ChangeEasyPassword(User user, string newPasswordSha1) + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { if (user == null) { throw new ArgumentNullException("user"); } - if (string.IsNullOrWhiteSpace(newPasswordSha1)) + + if (newPassword != null) + { + newPasswordHash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) { - throw new ArgumentNullException("newPasswordSha1"); + throw new ArgumentNullException("newPasswordHash"); } - user.EasyPassword = newPasswordSha1; + user.EasyPassword = newPasswordHash; UpdateUser(user); @@ -745,7 +784,8 @@ namespace Emby.Server.Implementations.Library Id = Guid.NewGuid(), DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true + UsesIdForConfigurationPath = true, + //Salt = BCrypt.GenerateSalt() }; } diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index b02c114bb..8c9377291 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -319,8 +319,7 @@ namespace Emby.Server.Implementations.Library var query = new InternalItemsQuery(user) { IncludeItemTypes = includeItemTypes, - SortOrder = SortOrder.Descending, - SortBy = new[] { ItemSortBy.DateCreated }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) }, IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null, ExcludeItemTypes = excludeItemTypes, IsVirtualItem = false, diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 2a2e1886f..f0578d9ef 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -42,6 +42,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile)); + using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { onStarted(); @@ -76,6 +78,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _logger.Info("Opened recording stream from tuner provider"); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile)); + using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { onStarted(); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 2e12f46bf..1975a6b01 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -305,26 +305,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var seriesTimers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); - List<ChannelInfo> channels = null; - foreach (var timer in seriesTimers) { - List<ProgramInfo> epgData; - - if (timer.RecordAnyChannel) - { - if (channels == null) - { - channels = (await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false)).ToList(); - } - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(timer.ChannelId); - } - await UpdateTimersForSeriesTimer(epgData, timer, false, true).ConfigureAwait(false); + await UpdateTimersForSeriesTimer(timer, false, true).ConfigureAwait(false); } } @@ -332,6 +315,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false); + var tempChannelCache = new Dictionary<string, LiveTvChannel>(); + foreach (var timer in timers) { if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id)) @@ -345,15 +330,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV continue; } - var epg = GetEpgDataForChannel(timer.ChannelId); - var program = epg.FirstOrDefault(i => string.Equals(i.Id, timer.ProgramId, StringComparison.OrdinalIgnoreCase)); + var program = GetProgramInfoFromCache(timer); if (program == null) { OnTimerOutOfDate(timer); continue; } - RecordingHelper.CopyProgramInfoToTimerInfo(program, timer); + CopyProgramInfoToTimerInfo(program, timer, tempChannelCache); _timerProvider.Update(timer); } } @@ -621,7 +605,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV ActiveRecordingInfo activeRecordingInfo; if (_activeRecordings.TryGetValue(timerId, out activeRecordingInfo)) - { + { activeRecordingInfo.Timer = timer; activeRecordingInfo.CancellationTokenSource.Cancel(); } @@ -672,11 +656,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV timer.Id = Guid.NewGuid().ToString("N"); - ProgramInfo programInfo = null; + LiveTvProgram programInfo = null; if (!string.IsNullOrWhiteSpace(timer.ProgramId)) { - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); + programInfo = GetProgramInfoFromCache(timer); } if (programInfo == null) { @@ -686,7 +670,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (programInfo != null) { - RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); + CopyProgramInfoToTimerInfo(programInfo, timer); } timer.IsManual = true; @@ -698,24 +682,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { info.Id = Guid.NewGuid().ToString("N"); - List<ProgramInfo> epgData; - if (info.RecordAnyChannel) - { - var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false); - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(info.ChannelId); - } - // populate info.seriesID - var program = epgData.FirstOrDefault(i => string.Equals(i.Id, info.ProgramId, StringComparison.OrdinalIgnoreCase)); + var program = GetProgramInfoFromCache(info.ProgramId); if (program != null) { - info.SeriesId = program.SeriesId; + info.SeriesId = program.ExternalSeriesId; } else { @@ -750,7 +722,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _timerProvider.AddOrUpdate(timer, false); } - await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false); + await UpdateTimersForSeriesTimer(info, true, false).ConfigureAwait(false); return info.Id; } @@ -779,19 +751,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _seriesTimerProvider.Update(instance); - List<ProgramInfo> epgData; - if (instance.RecordAnyChannel) - { - var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false); - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(instance.ChannelId); - } - - await UpdateTimersForSeriesTimer(epgData, instance, true, true).ConfigureAwait(false); + await UpdateTimersForSeriesTimer(instance, true, true).ConfigureAwait(false); } } @@ -962,23 +922,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return Task.FromResult((IEnumerable<SeriesTimerInfo>)_seriesTimerProvider.GetAll()); } - public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) - { - try - { - return await GetProgramsAsyncInternal(channelId, startDateUtc, endDateUtc, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error getting programs", ex); - return GetEpgDataForChannel(channelId).Where(i => i.StartDate <= endDateUtc && i.EndDate >= startDateUtc); - } - } - private bool IsListingProviderEnabledForTuner(ListingsProviderInfo info, string tunerHostId) { if (info.EnableAllTuners) @@ -994,7 +937,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase); } - private async Task<IEnumerable<ProgramInfo>> GetProgramsAsyncInternal(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) + public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) { var channels = await GetChannelsAsync(true, cancellationToken).ConfigureAwait(false); var channel = channels.First(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)); @@ -1037,8 +980,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (programs.Count > 0) { - SaveEpgDataForChannel(channelId, programs); - return programs; } } @@ -1464,11 +1405,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException("timer"); } - ProgramInfo programInfo = null; + LiveTvProgram programInfo = null; if (!string.IsNullOrWhiteSpace(timer.ProgramId)) { - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); + programInfo = GetProgramInfoFromCache(timer); } if (programInfo == null) { @@ -1478,8 +1419,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (programInfo != null) { - RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); - activeRecordingInfo.Program = programInfo; + CopyProgramInfoToTimerInfo(programInfo, timer); + //activeRecordingInfo.Program = programInfo; } string seriesPath = null; @@ -1488,14 +1429,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV string liveStreamId = null; - OnRecordingStatusChanged(); - try { var recorder = await GetRecorder().ConfigureAwait(false); var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); + _logger.Info("Opening recording stream from tuner provider"); var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) .ConfigureAwait(false); @@ -1509,23 +1449,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV recordPath = EnsureFileUnique(recordPath, timer.Id); _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(recordPath)); activeRecordingInfo.Path = recordPath; var duration = recordingEndDate - DateTime.UtcNow; - _logger.Info("Beginning recording. Will record for {0} minutes.", - duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); _logger.Info("Writing file to path: " + recordPath); - _logger.Info("Opening recording stream from tuner provider"); - Action onStarted = () => + Action onStarted = async () => { timer.Status = RecordingStatus.InProgress; _timerProvider.AddOrUpdate(timer, false); - SaveRecordingMetadata(timer, recordPath, seriesPath); + await SaveRecordingMetadata(timer, recordPath, seriesPath).ConfigureAwait(false); TriggerRefresh(recordPath); EnforceKeepUpTo(timer, seriesPath); }; @@ -1559,7 +1496,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } TriggerRefresh(recordPath); - _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); + _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false); ActiveRecordingInfo removed; _activeRecordings.TryRemove(timer.Id, out removed); @@ -1585,17 +1522,29 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _timerProvider.Delete(timer); } - - OnRecordingStatusChanged(); } private void TriggerRefresh(string path) { + _logger.Debug("Triggering refresh on {0}", path); + var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path)); if (item != null) { - item.ChangedExternally(); + _logger.Debug("Refreshing recording parent {0}", item.Path); + + _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem) + { + ValidateChildren = true, + RefreshPaths = new List<string> + { + path, + _fileSystem.GetDirectoryName(path), + _fileSystem.GetDirectoryName(_fileSystem.GetDirectoryName(path)) + } + + }, RefreshPriority.High); } } @@ -1603,6 +1552,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { BaseItem item = null; + var parentPath = _fileSystem.GetDirectoryName(path); + while (item == null && !string.IsNullOrEmpty(path)) { item = _libraryManager.FindByPath(path, null); @@ -1612,14 +1563,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (item != null) { - // If the item has been deleted find the first valid parent that still exists - while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) + if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase)) { - item = item.GetParent(); - - if (item == null) + var parentItem = item.GetParent(); + if (parentItem != null && !(parentItem is AggregateFolder)) { - break; + item = parentItem; } } } @@ -1627,14 +1576,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return item; } - private void OnRecordingStatusChanged() - { - EventHelper.FireEventIfNotNull(RecordingStatusChanged, this, new RecordingStatusChangedEventArgs - { - - }, _logger); - } - private async void EnforceKeepUpTo(TimerInfo timer, string seriesPath) { if (string.IsNullOrWhiteSpace(timer.SeriesTimerId)) @@ -1687,8 +1628,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery { - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) }, IsVirtualItem = false, IsFolder = false, Recursive = true, @@ -1810,7 +1750,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var config = GetConfiguration(); - var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false); + var regInfo = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false); if (regInfo.IsValid) { @@ -2020,7 +1960,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async void SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath) + private async Task SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath) { try { @@ -2337,18 +2277,49 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private ProgramInfo GetProgramInfoFromCache(string channelId, string programId) + private LiveTvProgram GetProgramInfoFromCache(string programId) { - var epgData = GetEpgDataForChannel(channelId); - return epgData.FirstOrDefault(p => string.Equals(p.Id, programId, StringComparison.OrdinalIgnoreCase)); + var query = new InternalItemsQuery + { + ItemIds = new[] { _liveTvManager.GetInternalProgramId(Name, programId).ToString("N") }, + Limit = 1, + DtoOptions = new DtoOptions() + }; + + return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().FirstOrDefault(); } - private ProgramInfo GetProgramInfoFromCache(string channelId, DateTime startDateUtc) + private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer) { - var epgData = GetEpgDataForChannel(channelId); - var startDateTicks = startDateUtc.Ticks; - // Find the first program that starts within 3 minutes - return epgData.FirstOrDefault(p => Math.Abs(startDateTicks - p.StartDate.Ticks) <= TimeSpan.FromMinutes(3).Ticks); + return GetProgramInfoFromCache(timer.ProgramId, timer.ChannelId); + } + + private LiveTvProgram GetProgramInfoFromCache(string programId, string channelId) + { + return GetProgramInfoFromCache(programId); + } + + private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc) + { + var query = new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, + Limit = 1, + DtoOptions = new DtoOptions(true) + { + EnableImages = false + }, + MinStartDate = startDateUtc.AddMinutes(-3), + MaxStartDate = startDateUtc.AddMinutes(3), + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) } + }; + + if (!string.IsNullOrWhiteSpace(channelId)) + { + query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, channelId).ToString("N") }; + } + + return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().FirstOrDefault(); } private LiveTvOptions GetConfiguration() @@ -2422,9 +2393,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers) + private async Task UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers) { - var allTimers = GetTimersForSeries(seriesTimer, epgData) + var allTimers = GetTimersForSeries(seriesTimer) .ToList(); var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); @@ -2521,23 +2492,160 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms) + private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer) { if (seriesTimer == null) { throw new ArgumentNullException("seriesTimer"); } - if (allPrograms == null) + + if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId)) { - throw new ArgumentNullException("allPrograms"); + return new List<TimerInfo>(); } - // Exclude programs that have already ended - allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow); + var query = new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, + ExternalSeriesId = seriesTimer.SeriesId, + DtoOptions = new DtoOptions(true) + { + EnableImages = false + }, + MinEndDate = DateTime.UtcNow + }; - allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); + if (!seriesTimer.RecordAnyChannel) + { + query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, seriesTimer.ChannelId).ToString("N") }; + } - return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer)); + var tempChannelCache = new Dictionary<string, LiveTvChannel>(); + + return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().Select(i => CreateTimer(i, seriesTimer, tempChannelCache)); + } + + private TimerInfo CreateTimer(LiveTvProgram parent, SeriesTimerInfo seriesTimer, Dictionary<string, LiveTvChannel> tempChannelCache) + { + string channelId = seriesTimer.RecordAnyChannel ? null : seriesTimer.ChannelId; + + if (string.IsNullOrWhiteSpace(channelId) && !string.IsNullOrWhiteSpace(parent.ChannelId)) + { + LiveTvChannel channel; + + if (!tempChannelCache.TryGetValue(parent.ChannelId, out channel)) + { + channel = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name }, + ItemIds = new[] { parent.ChannelId }, + DtoOptions = new DtoOptions() + + }).Cast<LiveTvChannel>().FirstOrDefault(); + + if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId)) + { + tempChannelCache[parent.ChannelId] = channel; + } + } + + if (channel != null || tempChannelCache.TryGetValue(parent.ChannelId, out channel)) + { + channelId = channel.ExternalId; + } + } + + var timer = new TimerInfo + { + ChannelId = channelId, + Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N"), + StartDate = parent.StartDate, + EndDate = parent.EndDate.Value, + ProgramId = parent.ExternalId, + PrePaddingSeconds = seriesTimer.PrePaddingSeconds, + PostPaddingSeconds = seriesTimer.PostPaddingSeconds, + IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired, + IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired, + KeepUntil = seriesTimer.KeepUntil, + Priority = seriesTimer.Priority, + Name = parent.Name, + Overview = parent.Overview, + SeriesId = parent.ExternalSeriesId, + SeriesTimerId = seriesTimer.Id, + ShowId = parent.ShowId + }; + + CopyProgramInfoToTimerInfo(parent, timer, tempChannelCache); + + return timer; + } + + private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo) + { + var tempChannelCache = new Dictionary<string, LiveTvChannel>(); + CopyProgramInfoToTimerInfo(programInfo, timerInfo, tempChannelCache); + } + + private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo, Dictionary<string, LiveTvChannel> tempChannelCache) + { + string channelId = null; + + if (!string.IsNullOrWhiteSpace(programInfo.ChannelId)) + { + LiveTvChannel channel; + + if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out channel)) + { + channel = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name }, + ItemIds = new[] { programInfo.ChannelId }, + DtoOptions = new DtoOptions() + + }).Cast<LiveTvChannel>().FirstOrDefault(); + + if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId)) + { + tempChannelCache[programInfo.ChannelId] = channel; + } + } + + if (channel != null || tempChannelCache.TryGetValue(programInfo.ChannelId, out channel)) + { + channelId = channel.ExternalId; + } + } + + timerInfo.Name = programInfo.Name; + timerInfo.StartDate = programInfo.StartDate; + timerInfo.EndDate = programInfo.EndDate.Value; + + if (!string.IsNullOrWhiteSpace(channelId)) + { + timerInfo.ChannelId = channelId; + } + + timerInfo.SeasonNumber = programInfo.ParentIndexNumber; + timerInfo.EpisodeNumber = programInfo.IndexNumber; + timerInfo.IsMovie = programInfo.IsMovie; + timerInfo.IsKids = programInfo.IsKids; + timerInfo.IsNews = programInfo.IsNews; + timerInfo.IsSports = programInfo.IsSports; + timerInfo.ProductionYear = programInfo.ProductionYear; + timerInfo.EpisodeTitle = programInfo.EpisodeTitle; + timerInfo.OriginalAirDate = programInfo.PremiereDate; + timerInfo.IsProgramSeries = programInfo.IsSeries; + + timerInfo.IsSeries = programInfo.IsSeries; + timerInfo.IsLive = programInfo.IsLive; + timerInfo.IsPremiere = programInfo.IsPremiere; + + timerInfo.HomePageUrl = programInfo.HomePageUrl; + timerInfo.CommunityRating = programInfo.CommunityRating; + timerInfo.Overview = programInfo.Overview; + timerInfo.OfficialRating = programInfo.OfficialRating; + timerInfo.IsRepeat = programInfo.IsRepeat; + timerInfo.SeriesId = programInfo.ExternalSeriesId; } private bool IsProgramAlreadyInLibrary(TimerInfo program) @@ -2578,51 +2686,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return false; } - private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms) - { - if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId)) - { - _logger.Error("seriesTimer.SeriesId is null. Cannot find programs for series"); - return new List<ProgramInfo>(); - } - - return allPrograms.Where(i => string.Equals(i.SeriesId, seriesTimer.SeriesId, StringComparison.OrdinalIgnoreCase)); - } - - private string GetChannelEpgCachePath(string channelId) - { - return Path.Combine(_config.CommonApplicationPaths.CachePath, "embytvepg", channelId + ".json"); - } - - private readonly object _epgLock = new object(); - private void SaveEpgDataForChannel(string channelId, List<ProgramInfo> epgData) - { - var path = GetChannelEpgCachePath(channelId); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); - lock (_epgLock) - { - _jsonSerializer.SerializeToFile(epgData, path); - } - } - private List<ProgramInfo> GetEpgDataForChannel(string channelId) - { - try - { - lock (_epgLock) - { - return _jsonSerializer.DeserializeFromFile<List<ProgramInfo>>(GetChannelEpgCachePath(channelId)); - } - } - catch - { - return new List<ProgramInfo>(); - } - } - private List<ProgramInfo> GetEpgDataForChannels(List<string> channelIds) - { - return channelIds.SelectMany(GetEpgDataForChannel).ToList(); - } - private bool _disposed; public void Dispose() { @@ -2631,6 +2694,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { pair.Value.CancellationTokenSource.Cancel(); } + GC.SuppressFinalize(this); } public List<VirtualFolderInfo> GetRecordingFolders() diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 48eba4117..149f69e5b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -284,8 +284,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV protected string GetOutputSizeParam() { var filters = new List<string>(); - - filters.Add("yadif=0:-1:0"); + + if (string.Equals(GetEncodingOptions().DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase)) + { + filters.Add("yadif=1:-1:0"); + } + else + { + filters.Add("yadif=0:-1:0"); + } var output = string.Empty; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs index 139cf570e..7c5f630a7 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Plugins; +using System; +using MediaBrowser.Controller.Plugins; namespace Emby.Server.Implementations.LiveTv.EmbyTV { @@ -11,6 +12,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index b5de6ef01..a5712b480 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,8 +1,6 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.LiveTv; using System; using System.Globalization; -using MediaBrowser.Model.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { @@ -13,63 +11,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return timer.StartDate.AddSeconds(-timer.PrePaddingSeconds); } - public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo seriesTimer) - { - var timer = new TimerInfo - { - ChannelId = parent.ChannelId, - Id = (seriesTimer.Id + parent.Id).GetMD5().ToString("N"), - StartDate = parent.StartDate, - EndDate = parent.EndDate, - ProgramId = parent.Id, - PrePaddingSeconds = seriesTimer.PrePaddingSeconds, - PostPaddingSeconds = seriesTimer.PostPaddingSeconds, - IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired, - IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired, - KeepUntil = seriesTimer.KeepUntil, - Priority = seriesTimer.Priority, - Name = parent.Name, - Overview = parent.Overview, - SeriesId = parent.SeriesId, - SeriesTimerId = seriesTimer.Id, - ShowId = parent.ShowId - }; - - CopyProgramInfoToTimerInfo(parent, timer); - - return timer; - } - - public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo) - { - timerInfo.Name = programInfo.Name; - timerInfo.StartDate = programInfo.StartDate; - timerInfo.EndDate = programInfo.EndDate; - timerInfo.ChannelId = programInfo.ChannelId; - - timerInfo.SeasonNumber = programInfo.SeasonNumber; - timerInfo.EpisodeNumber = programInfo.EpisodeNumber; - timerInfo.IsMovie = programInfo.IsMovie; - timerInfo.IsKids = programInfo.IsKids; - timerInfo.IsNews = programInfo.IsNews; - timerInfo.IsSports = programInfo.IsSports; - timerInfo.ProductionYear = programInfo.ProductionYear; - timerInfo.EpisodeTitle = programInfo.EpisodeTitle; - timerInfo.OriginalAirDate = programInfo.OriginalAirDate; - timerInfo.IsProgramSeries = programInfo.IsSeries; - - timerInfo.IsSeries = programInfo.IsSeries; - timerInfo.IsLive = programInfo.IsLive; - timerInfo.IsPremiere = programInfo.IsPremiere; - - timerInfo.HomePageUrl = programInfo.HomePageUrl; - timerInfo.CommunityRating = programInfo.CommunityRating; - timerInfo.Overview = programInfo.Overview; - timerInfo.OfficialRating = programInfo.OfficialRating; - timerInfo.IsRepeat = programInfo.IsRepeat; - timerInfo.SeriesId = programInfo.SeriesId; - } - public static string GetRecordingName(TimerInfo info) { var name = info.Name; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index eff2909fd..e6479feaa 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -9,14 +9,12 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.LiveTv { @@ -25,15 +23,13 @@ namespace Emby.Server.Implementations.LiveTv private readonly ILogger _logger; private readonly IImageProcessor _imageProcessor; - private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; private readonly IApplicationHost _appHost; private readonly ILibraryManager _libraryManager; - public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger, IApplicationHost appHost, ILibraryManager libraryManager) + public LiveTvDtoService(IDtoService dtoService, IImageProcessor imageProcessor, ILogger logger, IApplicationHost appHost, ILibraryManager libraryManager) { _dtoService = dtoService; - _userDataManager = userDataManager; _imageProcessor = imageProcessor; _logger = logger; _appHost = appHost; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index ac98d1043..38d2fd3c6 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.LiveTv _dtoService = dtoService; _userDataManager = userDataManager; - _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, appHost, _libraryManager); + _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, logger, appHost, _libraryManager); } /// <summary> @@ -187,7 +187,6 @@ namespace Emby.Server.Implementations.LiveTv IsSports = query.IsSports, IsSeries = query.IsSeries, IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }, - SortOrder = query.SortOrder ?? SortOrder.Ascending, TopParentIds = new[] { topFolder.Id.ToString("N") }, IsFavorite = query.IsFavorite, IsLiked = query.IsLiked, @@ -196,18 +195,22 @@ namespace Emby.Server.Implementations.LiveTv DtoOptions = dtoOptions }; - internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending))); + var orderBy = internalQuery.OrderBy.ToList(); + + orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending))); if (query.EnableFavoriteSorting) { - internalQuery.OrderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); + orderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); } if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase))) { - internalQuery.OrderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending)); + orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending)); } + internalQuery.OrderBy = orderBy.ToArray(); + return _libraryManager.GetItemsResult(internalQuery); } @@ -597,6 +600,12 @@ namespace Emby.Server.Implementations.LiveTv }; } + if (!string.Equals(info.ShowId, item.ShowId, StringComparison.OrdinalIgnoreCase)) + { + item.ShowId = info.ShowId; + forceUpdate = true; + } + var seriesId = info.SeriesId; if (!item.ParentId.Equals(channel.Id)) @@ -743,6 +752,11 @@ namespace Emby.Server.Implementations.LiveTv } } + if (isNew || isUpdated) + { + item.OnMetadataChanged(); + } + return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated); } @@ -829,8 +843,7 @@ namespace Emby.Server.Implementations.LiveTv item.SetImage(new ItemImageInfo { Path = info.ImagePath, - Type = ImageType.Primary, - IsPlaceholder = true + Type = ImageType.Primary }, 0); } else if (!string.IsNullOrWhiteSpace(info.ImageUrl)) @@ -838,8 +851,7 @@ namespace Emby.Server.Implementations.LiveTv item.SetImage(new ItemImageInfo { Path = info.ImageUrl, - Type = ImageType.Primary, - IsPlaceholder = true + Type = ImageType.Primary }, 0); } } @@ -918,10 +930,10 @@ namespace Emby.Server.Implementations.LiveTv var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); - if (query.SortBy.Length == 0) + if (query.OrderBy.Length == 0) { // Unless something else was specified, order by start date to take advantage of a specialized index - query.SortBy = new[] { ItemSortBy.StartDate }; + query.OrderBy = new Tuple<string, SortOrder>[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }; } RemoveFields(options); @@ -942,8 +954,7 @@ namespace Emby.Server.Implementations.LiveTv Genres = query.Genres, StartIndex = query.StartIndex, Limit = query.Limit, - SortBy = query.SortBy, - SortOrder = query.SortOrder ?? SortOrder.Ascending, + OrderBy = query.OrderBy, EnableTotalRecordCount = query.EnableTotalRecordCount, TopParentIds = new[] { topFolder.Id.ToString("N") }, Name = query.Name, @@ -985,8 +996,7 @@ namespace Emby.Server.Implementations.LiveTv var queryResult = _libraryManager.QueryItems(internalQuery); - var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user); var result = new QueryResult<BaseItemDto> { @@ -1013,7 +1023,7 @@ namespace Emby.Server.Implementations.LiveTv IsSports = query.IsSports, IsKids = query.IsKids, EnableTotalRecordCount = query.EnableTotalRecordCount, - SortBy = new[] { ItemSortBy.StartDate }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { topFolder.Id.ToString("N") }, DtoOptions = options }; @@ -1070,8 +1080,7 @@ namespace Emby.Server.Implementations.LiveTv var user = _userManager.GetUserById(query.UserId); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user); var result = new QueryResult<BaseItemDto> { @@ -1646,8 +1655,7 @@ namespace Emby.Server.Implementations.LiveTv IsVirtualItem = false, Limit = query.Limit, StartIndex = query.StartIndex, - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) }, EnableTotalRecordCount = query.EnableTotalRecordCount, IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count), ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count), @@ -1656,7 +1664,7 @@ namespace Emby.Server.Implementations.LiveTv }); } - public async Task<QueryResult<BaseItemDto>> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken) + public QueryResult<BaseItemDto> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); if (user != null && !IsLiveTvEnabled(user)) @@ -1694,16 +1702,14 @@ namespace Emby.Server.Implementations.LiveTv Recursive = true, AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(folders.Count), Limit = query.Limit, - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) }, EnableTotalRecordCount = query.EnableTotalRecordCount, IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count), ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count), DtoOptions = options }); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user); return new QueryResult<BaseItemDto> { @@ -1930,11 +1936,11 @@ namespace Emby.Server.Implementations.LiveTv var info = recording; - dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) + dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) || service == null ? null : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N"); - dto.TimerId = string.IsNullOrEmpty(info.TimerId) + dto.TimerId = string.IsNullOrEmpty(info.TimerId) || service == null ? null : _tvDtoService.GetInternalTimerId(service.Name, info.TimerId).ToString("N"); @@ -2040,8 +2046,7 @@ namespace Emby.Server.Implementations.LiveTv var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user); return new QueryResult<BaseItemDto> { @@ -2368,7 +2373,7 @@ namespace Emby.Server.Implementations.LiveTv }; } - public async Task AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> tuples, DtoOptions options, User user) + public void AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> tuples, DtoOptions options, User user) { var now = DateTime.UtcNow; @@ -2381,7 +2386,7 @@ namespace Emby.Server.Implementations.LiveTv MaxStartDate = now, MinEndDate = now, Limit = channelIds.Length, - SortBy = new[] { "StartDate" }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }, DtoOptions = options @@ -2425,7 +2430,7 @@ namespace Emby.Server.Implementations.LiveTv if (addCurrentProgram) { - var currentProgramDtos = await _dtoService.GetBaseItemDtos(currentProgramsList, options, user).ConfigureAwait(false); + var currentProgramDtos = _dtoService.GetBaseItemDtos(currentProgramsList, options, user); foreach (var programDto in currentProgramDtos) { @@ -2783,6 +2788,7 @@ namespace Emby.Server.Implementations.LiveTv public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } private bool _isDisposed = false; @@ -3146,5 +3152,15 @@ namespace Emby.Server.Implementations.LiveTv var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase)); return provider.GetChannels(info, cancellationToken); } + + public Guid GetInternalChannelId(string serviceName, string externalId) + { + return _tvDtoService.GetInternalChannelId(serviceName, externalId); + } + + public Guid GetInternalProgramId(string serviceName, string externalId) + { + return _tvDtoService.GetInternalProgramId(serviceName, externalId); + } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 0e52f874d..ed8b3074b 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -142,6 +142,8 @@ namespace Emby.Server.Implementations.LiveTv var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false); stream = info.Item1; directStreamProvider = info.Item2; + + //allowLiveStreamProbe = false; } else { diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index cad28c809..225360159 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv return new[] { // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(12).Ticks} + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} }; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index eae29bee7..f974b5c2c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun SupportsTranscoding = true, IsInfiniteStream = true, IgnoreDts = true, - //SupportsProbing = false, + SupportsProbing = false, //AnalyzeDurationMs = 2000000 //IgnoreIndex = true, //ReadAtNativeFramerate = true diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs index d2e9c8bf0..af064755d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs @@ -26,10 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); - private readonly MulticastStream _multicastStream; - private readonly string _tempFilePath; - private bool _enableFileBuffer = false; public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment) : base(mediaSource, environment, fileSystem) @@ -39,7 +36,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _appHost = appHost; OriginalStreamId = originalStreamId; - _multicastStream = new MulticastStream(_logger); _tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts"); } @@ -63,6 +59,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.Path = _tempFilePath; + //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.SupportsDirectPlay = false; //OpenedMediaSource.SupportsDirectStream = true; //OpenedMediaSource.SupportsTranscoding = true; @@ -107,19 +106,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { _logger.Info("Beginning multicastStream.CopyUntilCancelled"); - if (_enableFileBuffer) + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); + using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) { - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); - using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) - { - StreamHelper.CopyTo(response.Content, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken); - } - } - else - { - Resolve(openTaskCompletionSource); - - await _multicastStream.CopyUntilCancelled(response.Content, null, cancellationToken).ConfigureAwait(false); + StreamHelper.CopyTo(response.Content, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken); } } } @@ -144,7 +134,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } _liveStreamTaskCompletionSource.TrySetResult(true); - //await DeleteTempFile(_tempFilePath).ConfigureAwait(false); + await DeleteTempFile(_tempFilePath).ConfigureAwait(false); }); } @@ -158,56 +148,31 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - if (_enableFileBuffer) - { - return CopyFileTo(_tempFilePath, stream, cancellationToken); - } - return _multicastStream.CopyToAsync(stream, cancellationToken); - //return CopyFileTo(_tempFilePath, stream, cancellationToken); + return CopyFileTo(_tempFilePath, stream, cancellationToken); } protected async Task CopyFileTo(string path, Stream outputStream, CancellationToken cancellationToken) { long startPosition = -20000; - if (startPosition < 0) - { - var length = FileSystem.GetFileInfo(path).Length; - startPosition = Math.Max(length - startPosition, 0); - } _logger.Info("Live stream starting position is {0} bytes", startPosition.ToString(CultureInfo.InvariantCulture)); - var allowAsync = Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; + var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - using (var inputStream = GetInputStream(path, startPosition, allowAsync)) + using (var inputStream = (FileStream)GetInputStream(path, allowAsync)) { if (startPosition > 0) { - inputStream.Position = startPosition; + inputStream.Seek(-20000, SeekOrigin.End); } while (!cancellationToken.IsCancellationRequested) { - long bytesRead; - - if (allowAsync) - { - bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 2, cancellationToken).ConfigureAwait(false); - } - else - { - StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); - bytesRead = 1; - } + StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 41b058baf..c737c4cba 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -99,6 +99,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var task = StopStreaming(); Task.WaitAll(task); + GC.SuppressFinalize(this); } public async Task<bool> CheckTunerAvailability(IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 5ad6e2e16..6c21066fb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -34,8 +34,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly INetworkManager _networkManager; private readonly string _tempFilePath; - private bool _enableFileBuffer = false; - private readonly MulticastStream _multicastStream; 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) @@ -48,7 +46,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _channelCommands = channelCommands; _numTuners = numTuners; _tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts"); - _multicastStream = new MulticastStream(_logger); } protected override async Task OpenInternal(CancellationToken openCancellationToken) @@ -126,17 +123,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (!cancellationToken.IsCancellationRequested) { - if (_enableFileBuffer) + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); + using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) { - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); - using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) - { - CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken); - } - } - else - { - await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false); + CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken); } } } @@ -178,56 +168,33 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun }); } - public async Task CopyToAsync(Stream outputStream, CancellationToken cancellationToken) + public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - if (!_enableFileBuffer) - { - await _multicastStream.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); - return; - } - - var path = _tempFilePath; + return CopyFileTo(_tempFilePath, stream, cancellationToken); + } + protected async Task CopyFileTo(string path, Stream outputStream, CancellationToken cancellationToken) + { long startPosition = -20000; - if (startPosition < 0) - { - var length = FileSystem.GetFileInfo(path).Length; - startPosition = Math.Max(length - startPosition, 0); - } _logger.Info("Live stream starting position is {0} bytes", startPosition.ToString(CultureInfo.InvariantCulture)); - var allowAsync = Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; + var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - using (var inputStream = GetInputStream(path, startPosition, allowAsync)) + using (var inputStream = (FileStream)GetInputStream(path, allowAsync)) { if (startPosition > 0) { - inputStream.Position = startPosition; + inputStream.Seek(-20000, SeekOrigin.End); } while (!cancellationToken.IsCancellationRequested) { - long bytesRead; - - if (allowAsync) - { - bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 2, cancellationToken).ConfigureAwait(false); - } - else - { - StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); - bytesRead = 1; - } + StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } } } } @@ -285,22 +252,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun //return taskCompletion.Task; } - private void StreamCopyCallback(IAsyncResult result) - { - var copier = (AsyncStreamCopier)result.AsyncState; - var taskCompletion = copier.TaskCompletionSource; - - try - { - copier.EndCopy(result); - taskCompletion.TrySetResult(0); - } - catch (Exception ex) - { - taskCompletion.TrySetException(ex); - } - } - public class UdpClientStream : Stream { private static int RtpHeaderBytes = 12; diff --git a/Emby.Server.Implementations/Logging/SimpleLogManager.cs b/Emby.Server.Implementations/Logging/SimpleLogManager.cs index 3a6992657..6129f38c4 100644 --- a/Emby.Server.Implementations/Logging/SimpleLogManager.cs +++ b/Emby.Server.Implementations/Logging/SimpleLogManager.cs @@ -107,6 +107,7 @@ namespace Emby.Server.Implementations.Logging } _fileLogger = null; + GC.SuppressFinalize(this); } } @@ -130,13 +131,18 @@ namespace Emby.Server.Implementations.Logging private void LogInternal() { - while (!_cancellationTokenSource.IsCancellationRequested) + while (!_cancellationTokenSource.IsCancellationRequested && !_disposed) { try { foreach (var message in _queue.GetConsumingEnumerable()) { var bytes = Encoding.UTF8.GetBytes(message + Environment.NewLine); + if (_disposed) + { + return; + } + _fileStream.Write(bytes, 0, bytes.Length); _fileStream.Flush(true); @@ -166,17 +172,18 @@ namespace Emby.Server.Implementations.Logging return; } - _fileStream.Flush(); + _fileStream.Flush(true); } public void Dispose() { _cancellationTokenSource.Cancel(); - _disposed = true; + Flush(); - _fileStream.Flush(); + _disposed = true; _fileStream.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/Emby.Server.Implementations/Net/NetAcceptSocket.cs b/Emby.Server.Implementations/Net/NetAcceptSocket.cs index 936a66c0b..d80341a07 100644 --- a/Emby.Server.Implementations/Net/NetAcceptSocket.cs +++ b/Emby.Server.Implementations/Net/NetAcceptSocket.cs @@ -89,66 +89,10 @@ namespace Emby.Server.Implementations.Net Socket.Bind(nativeEndpoint); } - private SocketAcceptor _acceptor; - public void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed) - { - _acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed, DualMode); - - _acceptor.StartAccept(); - } - - public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken) - { - var options = TransmitFileOptions.UseDefaultWorkerThread; - - var completionSource = new TaskCompletionSource<bool>(); - - var result = Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), new Tuple<Socket, string, TaskCompletionSource<bool>>(Socket, path, completionSource)); - - return completionSource.Task; - } - - public IAsyncResult BeginSendFile(string path, byte[] preBuffer, byte[] postBuffer, AsyncCallback callback, object state) - { - var options = TransmitFileOptions.UseDefaultWorkerThread; - - return Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), state); - } - - public void EndSendFile(IAsyncResult result) - { - Socket.EndSendFile(result); - } - - private void FileSendCallback(IAsyncResult ar) - { - // Retrieve the socket from the state object. - Tuple<Socket, string, TaskCompletionSource<bool>> data = (Tuple<Socket, string, TaskCompletionSource<bool>>)ar.AsyncState; - - var client = data.Item1; - var path = data.Item2; - var taskCompletion = data.Item3; - - // Complete sending the data to the remote device. - try - { - client.EndSendFile(ar); - taskCompletion.TrySetResult(true); - } - catch (SocketException ex) - { - _logger.Info("Socket.SendFile failed for {0}. error code {1}", path, ex.SocketErrorCode); - taskCompletion.TrySetException(ex); - } - catch (Exception ex) - { - taskCompletion.TrySetException(ex); - } - } - public void Dispose() { Socket.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Net/SocketAcceptor.cs b/Emby.Server.Implementations/Net/SocketAcceptor.cs deleted file mode 100644 index 288ba93ad..000000000 --- a/Emby.Server.Implementations/Net/SocketAcceptor.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Net.Sockets; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; - -namespace Emby.Server.Implementations.Net -{ - public class SocketAcceptor - { - private readonly ILogger _logger; - private readonly Socket _originalSocket; - private readonly Func<bool> _isClosed; - private readonly Action<IAcceptSocket> _onAccept; - private readonly bool _isDualMode; - - public SocketAcceptor(ILogger logger, Socket originalSocket, Action<IAcceptSocket> onAccept, Func<bool> isClosed, bool isDualMode) - { - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - if (originalSocket == null) - { - throw new ArgumentNullException("originalSocket"); - } - if (onAccept == null) - { - throw new ArgumentNullException("onAccept"); - } - if (isClosed == null) - { - throw new ArgumentNullException("isClosed"); - } - - _logger = logger; - _originalSocket = originalSocket; - _isClosed = isClosed; - _isDualMode = isDualMode; - _onAccept = onAccept; - } - - public void StartAccept() - { - Socket dummy = null; - StartAccept(null, ref dummy); - } - - public void StartAccept(SocketAsyncEventArgs acceptEventArg, ref Socket accepted) - { - if (acceptEventArg == null) - { - acceptEventArg = new SocketAsyncEventArgs(); - acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed); - } - else - { - // acceptSocket must be cleared since the context object is being reused - acceptEventArg.AcceptSocket = null; - } - - try - { - bool willRaiseEvent = _originalSocket.AcceptAsync(acceptEventArg); - - if (!willRaiseEvent) - { - ProcessAccept(acceptEventArg); - } - } - catch (Exception ex) - { - if (accepted != null) - { - try - { -#if NET46 - accepted.Close(); -#else - accepted.Dispose(); -#endif - } - catch - { - } - accepted = null; - } - } - } - - // This method is the callback method associated with Socket.AcceptAsync - // operations and is invoked when an accept operation is complete - // - void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e) - { - ProcessAccept(e); - } - - private void ProcessAccept(SocketAsyncEventArgs e) - { - if (_isClosed()) - { - return; - } - - // http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx - // Under certain conditions ConnectionReset can occur - // Need to attept to re-accept - if (e.SocketError == SocketError.ConnectionReset) - { - _logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept."); - Socket dummy = null; - StartAccept(e, ref dummy); - return; - } - - var acceptSocket = e.AcceptSocket; - if (acceptSocket != null) - { - //ProcessAccept(acceptSocket); - _onAccept(new NetAcceptSocket(acceptSocket, _logger, _isDualMode)); - } - - // Accept the next connection request - StartAccept(e, ref acceptSocket); - } - } -} diff --git a/Emby.Server.Implementations/News/NewsEntryPoint.cs b/Emby.Server.Implementations/News/NewsEntryPoint.cs index 3c9a3bbf1..03c79c2c1 100644 --- a/Emby.Server.Implementations/News/NewsEntryPoint.cs +++ b/Emby.Server.Implementations/News/NewsEntryPoint.cs @@ -271,6 +271,7 @@ namespace Emby.Server.Implementations.News _timer.Dispose(); _timer = null; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Notifications/Notifications.cs b/Emby.Server.Implementations/Notifications/Notifications.cs index ac3cc7564..09c1f07e0 100644 --- a/Emby.Server.Implementations/Notifications/Notifications.cs +++ b/Emby.Server.Implementations/Notifications/Notifications.cs @@ -551,6 +551,7 @@ namespace Emby.Server.Implementations.Notifications _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; _userManager.UserLockedOut -= _userManager_UserLockedOut; + GC.SuppressFinalize(this); } private void DisposeLibraryUpdateTimer() diff --git a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs index 0d89ba84f..6e57e7f81 100644 --- a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs +++ b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Net; +using System; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Plugins; using System.Linq; @@ -49,6 +50,7 @@ namespace Emby.Server.Implementations.Notifications public void Dispose() { _notificationsRepo.NotificationAdded -= _notificationsRepo_NotificationAdded; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index c2e860339..36e8b4dd8 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Configuration; +using System; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.Playlists return subItem; } - var parent = subItem.GetParent(); + var parent = subItem.IsOwnedItem ? subItem.GetOwner() : subItem.GetParent(); if (parent != null && parent.HasImage(ImageType.Primary)) { @@ -86,7 +87,7 @@ namespace Emby.Server.Implementations.Playlists { Genres = new[] { item.Name }, IncludeItemTypes = new[] { typeof(MusicAlbum).Name, typeof(MusicVideo).Name, typeof(Audio).Name }, - SortBy = new[] { ItemSortBy.Random }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, ImageTypes = new[] { ImageType.Primary }, @@ -118,7 +119,7 @@ namespace Emby.Server.Implementations.Playlists { Genres = new[] { item.Name }, IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name }, - SortBy = new[] { ItemSortBy.Random }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, ImageTypes = new[] { ImageType.Primary }, diff --git a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index ec371c741..bf7bf9ff8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -58,8 +58,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - + return new[] { + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, @@ -88,6 +88,12 @@ namespace Emby.Server.Implementations.ScheduledTasks IsFolder = false, Recursive = true, DtoOptions = new DtoOptions(false) + { + EnableImages = false + }, + SourceTypes = new SourceType[] { SourceType.Library }, + HasChapterImages = false, + IsVirtualItem = false }) .OfType<Video>() diff --git a/Emby.Server.Implementations/ServerManager/ServerManager.cs b/Emby.Server.Implementations/ServerManager/ServerManager.cs index 7cd94c526..b267f928b 100644 --- a/Emby.Server.Implementations/ServerManager/ServerManager.cs +++ b/Emby.Server.Implementations/ServerManager/ServerManager.cs @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.ServerManager /// <summary> /// Starts this instance. /// </summary> - public void Start(IEnumerable<string> urlPrefixes) + public void Start(string[] urlPrefixes) { ReloadHttpServer(urlPrefixes); } @@ -120,7 +120,7 @@ namespace Emby.Server.Implementations.ServerManager /// <summary> /// Restarts the Http Server, or starts it if not currently running /// </summary> - private void ReloadHttpServer(IEnumerable<string> urlPrefixes) + private void ReloadHttpServer(string[] urlPrefixes) { _logger.Info("Loading Http Server"); diff --git a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs b/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs index 4d5192fea..076f50d93 100644 --- a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs +++ b/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs @@ -136,7 +136,7 @@ namespace Emby.Server.Implementations.ServerManager return; } - var charset = _textEncoding.GetDetectedEncodingName(bytes, null, false); + var charset = _textEncoding.GetDetectedEncodingName(bytes, bytes.Length, null, false); if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index dfad09f7b..91314c15a 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -45,10 +45,15 @@ namespace Emby.Server.Implementations.Services var bytesResponse = this.Response as byte[]; if (bytesResponse != null) { + var contentLength = bytesResponse.Length; + if (response != null) - response.SetContentLength(bytesResponse.Length); + response.SetContentLength(contentLength); - await responseStream.WriteAsync(bytesResponse, 0, bytesResponse.Length).ConfigureAwait(false); + if (contentLength > 0) + { + await responseStream.WriteAsync(bytesResponse, 0, contentLength).ConfigureAwait(false); + } return; } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index 84dc343c3..22e1bc4aa 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -41,11 +41,11 @@ namespace Emby.Server.Implementations.Services response.StatusCode = httpResult.Status; response.StatusDescription = httpResult.StatusCode.ToString(); - if (string.IsNullOrEmpty(httpResult.ContentType)) - { - httpResult.ContentType = defaultContentType; - } - response.ContentType = httpResult.ContentType; + //if (string.IsNullOrEmpty(httpResult.ContentType)) + //{ + // httpResult.ContentType = defaultContentType; + //} + //response.ContentType = httpResult.ContentType; if (httpResult.Cookies != null) { @@ -124,7 +124,10 @@ namespace Emby.Server.Implementations.Services response.ContentType = "application/octet-stream"; response.SetContentLength(bytes.Length); - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + if (bytes.Length > 0) + { + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + } return; } @@ -133,7 +136,10 @@ namespace Emby.Server.Implementations.Services { bytes = Encoding.UTF8.GetBytes(responseText); response.SetContentLength(bytes.Length); - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + if (bytes.Length > 0) + { + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + } return; } @@ -150,8 +156,15 @@ namespace Emby.Server.Implementations.Services serializer(result, ms); ms.Position = 0; - response.SetContentLength(ms.Length); - await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); + + var contentLength = ms.Length; + + response.SetContentLength(contentLength); + + if (contentLength > 0) + { + await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); + } } //serializer(result, outputStream); diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index c3970b22f..3fd6d88f8 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -75,11 +75,7 @@ namespace Emby.Server.Implementations.Services var attrs = appHost.GetRouteAttributes(requestType); foreach (RouteAttribute attr in attrs) { - var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes); - - if (!restPath.IsValid) - throw new NotSupportedException(string.Format( - "RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetMethodName())); + var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.IsHidden, attr.Summary, attr.Description); RegisterRestPath(restPath); } @@ -92,8 +88,7 @@ namespace Emby.Server.Implementations.Services if (!restPath.Path.StartsWith("/")) throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName())); if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1) - throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " + - "See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetMethodName())); + throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName())); List<RestPath> pathsAtFirstMatch; if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch)) diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 4a2199c89..5709d3e0a 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Services { public static class ServiceExecExtensions { - public static HashSet<string> AllVerbs = new HashSet<string>(new[] { + public static string[] AllVerbs = new[] { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616 "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518 "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT", @@ -22,27 +22,43 @@ namespace Emby.Server.Implementations.Services "SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/ "BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY", "POLL", "SUBSCRIBE", "UNSUBSCRIBE" - }); + }; - public static IEnumerable<MethodInfo> GetActions(this Type serviceType) + public static HashSet<string> AllVerbsSet = new HashSet<string>(AllVerbs); + + public static List<MethodInfo> GetActions(this Type serviceType) { - foreach (var mi in serviceType.GetRuntimeMethods().Where(i => i.IsPublic && !i.IsStatic)) + var list = new List<MethodInfo>(); + + foreach (var mi in serviceType.GetRuntimeMethods()) { + if (!mi.IsPublic) + { + continue; + } + + if (mi.IsStatic) + { + continue; + } + if (mi.GetParameters().Length != 1) continue; var actionName = mi.Name; - if (!AllVerbs.Contains(actionName, StringComparer.OrdinalIgnoreCase) && !string.Equals(actionName, ServiceMethod.AnyAction, StringComparison.OrdinalIgnoreCase)) + if (!AllVerbs.Contains(actionName, StringComparer.OrdinalIgnoreCase)) continue; - yield return mi; + list.Add(mi); } + + return list; } } internal static class ServiceExecGeneral { - public static Dictionary<string, ServiceMethod> execMap = new Dictionary<string, ServiceMethod>(); + private static Dictionary<string, ServiceMethod> execMap = new Dictionary<string, ServiceMethod>(); public static void CreateServiceRunnersFor(Type requestType, List<ServiceMethod> actions) { @@ -59,8 +75,7 @@ namespace Emby.Server.Implementations.Services var actionName = request.Verb ?? "POST"; ServiceMethod actionContext; - if (ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.Key(serviceType, actionName, requestName), out actionContext) - || ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.AnyKey(serviceType, requestName), out actionContext)) + if (ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.Key(serviceType, actionName, requestName), out actionContext)) { if (actionContext.RequestFilters != null) { diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index f9fcfdbab..d500595ce 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -162,7 +162,11 @@ namespace Emby.Server.Implementations.Services if (RequireqRequestStream(requestType)) { // Used by IRequiresRequestStream - return CreateRequiresRequestStreamRequest(host, httpReq, requestType); + var request = ServiceHandler.CreateRequest(httpReq, restPath, GetRequestParams(httpReq), host.CreateInstance(requestType)); + + var rawReq = (IRequiresRequestStream)request; + rawReq.RequestStream = httpReq.InputStream; + return rawReq; } var requestParams = GetFlattenedRequestParams(httpReq); @@ -176,16 +180,6 @@ namespace Emby.Server.Implementations.Services return requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo()); } - private static IRequiresRequestStream CreateRequiresRequestStreamRequest(HttpListenerHost host, IRequest req, Type requestType) - { - var restPath = GetRoute(req); - var request = ServiceHandler.CreateRequest(req, restPath, GetRequestParams(req), host.CreateInstance(requestType)); - - var rawReq = (IRequiresRequestStream)request; - rawReq.RequestStream = req.InputStream; - return rawReq; - } - public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams) { var requestDto = CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType); @@ -228,22 +222,26 @@ namespace Emby.Server.Implementations.Services } } - if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")) && request.FormData != null) + if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) { - foreach (var name in request.FormData.Keys) + var formData = request.FormData; + if (formData != null) { - if (name == null) continue; //thank you ASP.NET - - var values = request.FormData.GetValues(name); - if (values.Count == 1) + foreach (var name in formData.Keys) { - map[name] = values[0]; - } - else - { - for (var i = 0; i < values.Count; i++) + if (name == null) continue; //thank you ASP.NET + + var values = formData.GetValues(name); + if (values.Count == 1) + { + map[name] = values[0]; + } + else { - map[name + (i == 0 ? "" : "#" + i)] = values[i]; + for (var i = 0; i < values.Count; i++) + { + map[name + (i == 0 ? "" : "#" + i)] = values[i]; + } } } } @@ -270,12 +268,16 @@ namespace Emby.Server.Implementations.Services map[name] = request.QueryString[name]; } - if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")) && request.FormData != null) + if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) { - foreach (var name in request.FormData.Keys) + var formData = request.FormData; + if (formData != null) { - if (name == null) continue; //thank you ASP.NET - map[name] = request.FormData[name]; + foreach (var name in formData.Keys) + { + if (name == null) continue; //thank you ASP.NET + map[name] = formData[name]; + } } } diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs index bcbc6fb57..fa2dd43d0 100644 --- a/Emby.Server.Implementations/Services/ServiceMethod.cs +++ b/Emby.Server.Implementations/Services/ServiceMethod.cs @@ -4,8 +4,6 @@ namespace Emby.Server.Implementations.Services { public class ServiceMethod { - public const string AnyAction = "ANY"; - public string Id { get; set; } public ActionInvokerFn ServiceAction { get; set; } @@ -15,10 +13,5 @@ namespace Emby.Server.Implementations.Services { return serviceType.FullName + " " + method.ToUpper() + " " + requestDtoName; } - - public static string AnyKey(Type serviceType, string requestDtoName) - { - return Key(serviceType, AnyAction, requestDtoName); - } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index da5e74f1f..0ca36df19 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -21,8 +21,6 @@ namespace Emby.Server.Implementations.Services readonly bool[] componentsWithSeparators; private readonly string restPath; - private readonly string allowedVerbs; - private readonly bool allowsAllVerbs; public bool IsWildCardPath { get; private set; } private readonly string[] literalsToMatch; @@ -46,35 +44,21 @@ namespace Emby.Server.Implementations.Services /// </summary> public int TotalComponentsCount { get; set; } - public string[] Verbs - { - get - { - return allowsAllVerbs - ? new[] { "ANY" } - : AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); - } - } + public string[] Verbs { get; private set; } public Type RequestType { get; private set; } public string Path { get { return this.restPath; } } public string Summary { get; private set; } - - public string Notes { get; private set; } - - public bool AllowsAllVerbs { get { return this.allowsAllVerbs; } } - - public string AllowedVerbs { get { return this.allowedVerbs; } } + public string Description { get; private set; } + public bool IsHidden { get; private set; } public int Priority { get; set; } //passed back to RouteAttribute public static string[] GetPathPartsForMatching(string pathInfo) { - var parts = pathInfo.ToLower().Split(PathSeperatorChar) - .Where(x => !String.IsNullOrEmpty(x)).ToArray(); - return parts; + return pathInfo.ToLower().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); } public static List<string> GetFirstMatchHashKeys(string[] pathPartsForMatching) @@ -109,18 +93,15 @@ namespace Emby.Server.Implementations.Services return list; } - public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, string summary = null, string notes = null) + public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, bool isHidden = false, string summary = null, string description = null) { this.RequestType = requestType; this.Summary = summary; - this.Notes = notes; + this.IsHidden = isHidden; + this.Description = description; this.restPath = path; - this.allowsAllVerbs = verbs == null || String.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase); - if (!this.allowsAllVerbs) - { - this.allowedVerbs = verbs.ToUpper(); - } + this.Verbs = string.IsNullOrWhiteSpace(verbs) ? ServiceExecExtensions.AllVerbs : verbs.ToUpper().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries); var componentsList = new List<string>(); @@ -153,7 +134,6 @@ namespace Emby.Server.Implementations.Services this.PathComponentsCount = this.componentsWithSeparators.Length; string firstLiteralMatch = null; - var sbHashKey = new StringBuilder(); for (var i = 0; i < components.Length; i++) { var component = components[i]; @@ -172,7 +152,6 @@ namespace Emby.Server.Implementations.Services else { this.literalsToMatch[i] = component.ToLower(); - sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch); if (firstLiteralMatch == null) { @@ -198,9 +177,6 @@ namespace Emby.Server.Implementations.Services ? this.PathComponentsCount + PathSeperator + firstLiteralMatch : WildCardChar + PathSeperator + firstLiteralMatch; - this.IsValid = sbHashKey.Length > 0; - this.UniqueMatchHashKey = sbHashKey.ToString(); - this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType); RegisterCaseInsenstivePropertyNameMappings(); } @@ -220,26 +196,46 @@ namespace Emby.Server.Implementations.Services }; - private static List<Type> _excludeTypes = new List<Type> { typeof(Stream) }; + private static Type excludeType = typeof(Stream); - internal static PropertyInfo[] GetSerializableProperties(Type type) + internal static List<PropertyInfo> GetSerializableProperties(Type type) { - var properties = GetPublicProperties(type); - var readableProperties = properties.Where(x => x.GetMethod != null); + var list = new List<PropertyInfo>(); + var props = GetPublicProperties(type); - // else return those properties that are not decorated with IgnoreDataMember - return readableProperties - .Where(prop => prop.GetCustomAttributes(true) - .All(attr => + foreach (var prop in props) + { + if (prop.GetMethod == null) + { + continue; + } + + if (excludeType == prop.PropertyType) + { + continue; + } + + var ignored = false; + foreach (var attr in prop.GetCustomAttributes(true)) + { + if (IgnoreAttributesNamed.Contains(attr.GetType().Name)) { - var name = attr.GetType().Name; - return !IgnoreAttributesNamed.Contains(name); - })) - .Where(prop => !_excludeTypes.Contains(prop.PropertyType)) - .ToArray(); + ignored = true; + break; + } + } + + if (!ignored) + { + list.Add(prop); + } + } + + // else return those properties that are not decorated with IgnoreDataMember + return list; } - private static PropertyInfo[] GetPublicProperties(Type type) + private static List<PropertyInfo> GetPublicProperties(Type type) { if (type.GetTypeInfo().IsInterface) { @@ -269,12 +265,19 @@ namespace Emby.Server.Implementations.Services propertyInfos.InsertRange(0, newPropertyInfos); } - return propertyInfos.ToArray(propertyInfos.Count); + return propertyInfos; } - return GetTypesPublicProperties(type) - .Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties - .ToArray(); + var list = new List<PropertyInfo>(); + + foreach (var t in GetTypesPublicProperties(type)) + { + if (t.GetIndexParameters().Length == 0) + { + list.Add(t); + } + } + return list; } private static PropertyInfo[] GetTypesPublicProperties(Type subType) @@ -289,16 +292,11 @@ namespace Emby.Server.Implementations.Services return pis.ToArray(pis.Count); } - - public bool IsValid { get; set; } - /// <summary> /// Provide for quick lookups based on hashes that can be determined from a request url /// </summary> public string FirstMatchHashKey { get; private set; } - public string UniqueMatchHashKey { get; private set; } - private readonly StringMapTypeDeserializer typeDeserializer; private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>(); @@ -321,8 +319,14 @@ namespace Emby.Server.Implementations.Services score += Math.Max((10 - VariableArgsCount), 1) * 100; //Exact verb match is better than ANY - var exactVerb = String.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase); - score += exactVerb ? 10 : 1; + if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase)) + { + score += 10; + } + else + { + score += 1; + } return score; } @@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Services return false; } - if (!this.allowsAllVerbs && !StringContains(this.allowedVerbs, httpMethod)) + if (!Verbs.Contains(httpMethod, StringComparer.OrdinalIgnoreCase)) { //logger.Info("allowsAllVerbs mismatch for {0} for {1} allowedverbs {2}", httpMethod, string.Join("/", withPathInfoParts), this.allowedVerbs); return false; @@ -457,8 +461,7 @@ namespace Emby.Server.Implementations.Services public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance) { - var requestComponents = pathInfo.Split(PathSeperatorChar) - .Where(x => !String.IsNullOrEmpty(x)).ToArray(); + var requestComponents = pathInfo.Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); ExplodeComponents(ref requestComponents); @@ -555,10 +558,5 @@ namespace Emby.Server.Implementations.Services return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap); } - - public override int GetHashCode() - { - return UniqueMatchHashKey.GetHashCode(); - } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index fc1cf4ed9..2233bf918 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; namespace Emby.Server.Implementations.Services @@ -64,11 +63,16 @@ namespace Emby.Server.Implementations.Services if (instance == null) instance = _CreateInstanceFn(type); - foreach (var pair in keyValuePairs.Where(x => !string.IsNullOrEmpty(x.Value))) + foreach (var pair in keyValuePairs) { propertyName = pair.Key; propertyTextValue = pair.Value; + if (string.IsNullOrEmpty(propertyTextValue)) + { + continue; + } + if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)) { if (propertyName == "v") @@ -115,7 +119,7 @@ namespace Emby.Server.Implementations.Services { public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) { - if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Any()) return null; + if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null; var setMethodInfo = propertyInfo.SetMethod; return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs new file mode 100644 index 000000000..fc2bdbd55 --- /dev/null +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.Services +{ + [Route("/swagger", "GET", Summary = "Gets the swagger specifications")] + [Route("/swagger.json", "GET", Summary = "Gets the swagger specifications")] + public class GetSwaggerSpec : IReturn<SwaggerSpec> + { + } + + public class SwaggerSpec + { + public string swagger { get; set; } + public string[] schemes { get; set; } + public SwaggerInfo info { get; set; } + public string host { get; set; } + public string basePath { get; set; } + public SwaggerTag[] tags { get; set; } + public IDictionary<string, Dictionary<string, SwaggerMethod>> paths { get; set; } + public Dictionary<string, SwaggerDefinition> definitions { get; set; } + public SwaggerComponents components { get; set; } + } + + public class SwaggerComponents + { + public Dictionary<string, SwaggerSecurityScheme> securitySchemes { get; set; } + } + + public class SwaggerSecurityScheme + { + public string name { get; set; } + public string type { get; set; } + public string @in { get; set; } + } + + public class SwaggerInfo + { + public string description { get; set; } + public string version { get; set; } + public string title { get; set; } + public string termsOfService { get; set; } + + public SwaggerConcactInfo contact { get; set; } + } + + public class SwaggerConcactInfo + { + public string email { get; set; } + public string name { get; set; } + public string url { get; set; } + } + + public class SwaggerTag + { + public string description { get; set; } + public string name { get; set; } + } + + public class SwaggerMethod + { + public string summary { get; set; } + public string description { get; set; } + public string[] tags { get; set; } + public string operationId { get; set; } + public string[] consumes { get; set; } + public string[] produces { get; set; } + public SwaggerParam[] parameters { get; set; } + public Dictionary<string, SwaggerResponse> responses { get; set; } + public Dictionary<string, string[]>[] security { get; set; } + } + + public class SwaggerParam + { + public string @in { get; set; } + public string name { get; set; } + public string description { get; set; } + public bool required { get; set; } + public string type { get; set; } + public string collectionFormat { get; set; } + } + + public class SwaggerResponse + { + public string description { get; set; } + + // ex. "$ref":"#/definitions/Pet" + public Dictionary<string, string> schema { get; set; } + } + + public class SwaggerDefinition + { + public string type { get; set; } + public Dictionary<string, SwaggerProperty> properties { get; set; } + } + + public class SwaggerProperty + { + public string type { get; set; } + public string format { get; set; } + public string description { get; set; } + public string[] @enum { get; set; } + public string @default { get; set; } + } + + public class SwaggerService : IService, IRequiresRequest + { + private SwaggerSpec _spec; + + public IRequest Request { get; set; } + + public object Get(GetSwaggerSpec request) + { + return _spec ?? (_spec = GetSpec()); + } + + private SwaggerSpec GetSpec() + { + string host = null; + Uri uri; + if (Uri.TryCreate(Request.RawUrl, UriKind.Absolute, out uri)) + { + host = uri.Host; + } + + var securitySchemes = new Dictionary<string, SwaggerSecurityScheme>(); + + securitySchemes["api_key"] = new SwaggerSecurityScheme + { + name = "api_key", + type = "apiKey", + @in = "query" + }; + + var spec = new SwaggerSpec + { + schemes = new[] { "http" }, + tags = GetTags(), + swagger = "2.0", + info = new SwaggerInfo + { + title = "Emby Server API", + version = "1.0.0", + description = "Explore the Emby Server API", + contact = new SwaggerConcactInfo + { + name = "Emby Developer Community", + url = "https://emby.media/community/index.php?/forum/47-developer-api" + }, + termsOfService = "https://emby.media/terms" + }, + paths = GetPaths(), + definitions = GetDefinitions(), + basePath = "/emby", + host = host, + + components = new SwaggerComponents + { + securitySchemes = securitySchemes + } + }; + + return spec; + } + + + private SwaggerTag[] GetTags() + { + return new SwaggerTag[] { }; + } + + private Dictionary<string, SwaggerDefinition> GetDefinitions() + { + return new Dictionary<string, SwaggerDefinition>(); + } + + private IDictionary<string, Dictionary<string, SwaggerMethod>> GetPaths() + { + var paths = new SortedDictionary<string, Dictionary<string, SwaggerMethod>>(); + + var all = ServiceController.Instance.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList(); + + foreach (var current in all) + { + foreach (var info in current.Value) + { + if (info.IsHidden) + { + continue; + } + + if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + if (info.Path.StartsWith("/emby", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + paths[info.Path] = GetPathInfo(info); + } + } + + return paths; + } + + private Dictionary<string, SwaggerMethod> GetPathInfo(RestPath info) + { + var result = new Dictionary<string, SwaggerMethod>(); + + foreach (var verb in info.Verbs) + { + var responses = new Dictionary<string, SwaggerResponse> + { + }; + + responses["200"] = new SwaggerResponse + { + description = "OK" + }; + + var security = new List<Dictionary<string, string[]>>(); + + var apiKeySecurity = new Dictionary<string, string[]>(); + apiKeySecurity["api_key"] = new string[] { }; + + security.Add(apiKeySecurity); + + result[verb.ToLower()] = new SwaggerMethod + { + summary = info.Summary, + description = info.Description, + produces = new[] + { + "application/json" + }, + consumes = new[] + { + "application/json" + }, + operationId = info.RequestType.Name, + tags = new string[] { }, + + parameters = new SwaggerParam[] { }, + + responses = responses, + + security = security.ToArray() + }; + } + + return result; + } + } +} diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index dbac76bb4..bd53da1bd 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace Emby.Server.Implementations.Session { - public class HttpSessionController : ISessionController, IDisposable + public class HttpSessionController : ISessionController { private readonly IHttpClient _httpClient; private readonly IJsonSerializer _json; @@ -195,9 +195,5 @@ namespace Emby.Server.Implementations.Session return "?" + args; } - - public void Dispose() - { - } } } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 0692a0ba5..6f185bc81 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1416,7 +1416,7 @@ namespace Emby.Server.Implementations.Session if (enforcePassword) { - var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false); + var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false); if (result == null) { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 2735bb237..a5af843db 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -102,6 +102,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { _serverManager.WebSocketConnected -= _serverManager_WebSocketConnected; + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index f0ff0b5dd..ee9ee8969 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -283,6 +283,7 @@ namespace Emby.Server.Implementations.Session { socket.Closed -= connection_Closed; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/TV/SeriesPostScanTask.cs b/Emby.Server.Implementations/TV/SeriesPostScanTask.cs index 23b6a3cb5..764df8baf 100644 --- a/Emby.Server.Implementations/TV/SeriesPostScanTask.cs +++ b/Emby.Server.Implementations/TV/SeriesPostScanTask.cs @@ -217,6 +217,7 @@ namespace Emby.Server.Implementations.TV public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 018e452be..0b81f7e93 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -64,8 +64,7 @@ namespace Emby.Server.Implementations.TV var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.DatePlayed }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) }, SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, ParentId = parentIdGuid, @@ -122,8 +121,7 @@ namespace Emby.Server.Implementations.TV var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.DatePlayed }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) }, SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions @@ -200,8 +198,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.SortName }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) }, IsPlayed = true, Limit = 1, ParentIndexNumberNotEquals = 0, @@ -223,8 +220,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.SortName }, - SortOrder = SortOrder.Ascending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }, Limit = 1, IsPlayed = false, IsVirtualItem = false, diff --git a/Emby.Server.Implementations/TextEncoding/TextEncoding.cs b/Emby.Server.Implementations/TextEncoding/TextEncoding.cs index 1496d6f0f..9eb9be7ea 100644 --- a/Emby.Server.Implementations/TextEncoding/TextEncoding.cs +++ b/Emby.Server.Implementations/TextEncoding/TextEncoding.cs @@ -27,18 +27,33 @@ namespace Emby.Server.Implementations.TextEncoding return Encoding.ASCII; } - private Encoding GetInitialEncoding(byte[] buffer) + private Encoding GetInitialEncoding(byte[] buffer, int count) { - if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) - return Encoding.UTF8; - if (buffer[0] == 0xfe && buffer[1] == 0xff) - return Encoding.Unicode; - if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) - return Encoding.UTF32; - if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) - return Encoding.UTF7; + if (count >= 3) + { + if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) + return Encoding.UTF8; + } + + if (count >= 2) + { + if (buffer[0] == 0xfe && buffer[1] == 0xff) + return Encoding.Unicode; + } + + if (count >= 4) + { + if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) + return Encoding.UTF32; + } - var result = new TextEncodingDetect().DetectEncoding(buffer, buffer.Length); + if (count >= 3) + { + if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) + return Encoding.UTF7; + } + + var result = new TextEncodingDetect().DetectEncoding(buffer, count); switch (result) { @@ -64,9 +79,11 @@ namespace Emby.Server.Implementations.TextEncoding } private bool _langDetectInitialized; - public string GetDetectedEncodingName(byte[] bytes, string language, bool enableLanguageDetection) + public string GetDetectedEncodingName(byte[] bytes, int count, string language, bool enableLanguageDetection) { - var encoding = GetInitialEncoding(bytes); + var index = 0; + + var encoding = GetInitialEncoding(bytes, count); if (encoding != null && encoding.Equals(Encoding.UTF8)) { @@ -81,7 +98,7 @@ namespace Emby.Server.Implementations.TextEncoding LanguageDetector.Initialize(_json); } - language = DetectLanguage(bytes); + language = DetectLanguage(bytes, index, count); if (!string.IsNullOrWhiteSpace(language)) { @@ -89,7 +106,7 @@ namespace Emby.Server.Implementations.TextEncoding } } - var charset = DetectCharset(bytes, language); + var charset = DetectCharset(bytes, index, count, language); if (!string.IsNullOrWhiteSpace(charset)) { @@ -112,11 +129,11 @@ namespace Emby.Server.Implementations.TextEncoding return null; } - private string DetectLanguage(byte[] bytes) + private string DetectLanguage(byte[] bytes, int index, int count) { try { - return LanguageDetector.DetectLanguage(Encoding.UTF8.GetString(bytes)); + return LanguageDetector.DetectLanguage(Encoding.UTF8.GetString(bytes, index, count)); } catch (NLangDetectException ex) { @@ -124,7 +141,7 @@ namespace Emby.Server.Implementations.TextEncoding try { - return LanguageDetector.DetectLanguage(Encoding.ASCII.GetString(bytes)); + return LanguageDetector.DetectLanguage(Encoding.ASCII.GetString(bytes, index, count)); } catch (NLangDetectException ex) { @@ -132,7 +149,7 @@ namespace Emby.Server.Implementations.TextEncoding try { - return LanguageDetector.DetectLanguage(Encoding.Unicode.GetString(bytes)); + return LanguageDetector.DetectLanguage(Encoding.Unicode.GetString(bytes, index, count)); } catch (NLangDetectException ex) { @@ -163,9 +180,9 @@ namespace Emby.Server.Implementations.TextEncoding } } - public Encoding GetDetectedEncoding(byte[] bytes, string language, bool enableLanguageDetection) + public Encoding GetDetectedEncoding(byte[] bytes, int size, string language, bool enableLanguageDetection) { - var charset = GetDetectedEncodingName(bytes, language, enableLanguageDetection); + var charset = GetDetectedEncodingName(bytes, size, language, enableLanguageDetection); return GetEncodingFromCharset(charset); } @@ -225,10 +242,10 @@ namespace Emby.Server.Implementations.TextEncoding } } - private string DetectCharset(byte[] bytes, string language) + private string DetectCharset(byte[] bytes, int index, int count, string language) { var detector = new CharsetDetector(); - detector.Feed(bytes, 0, bytes.Length); + detector.Feed(bytes, index, count); detector.DataEnd(); var charset = detector.Charset; diff --git a/Emby.Server.Implementations/Threading/CommonTimer.cs b/Emby.Server.Implementations/Threading/CommonTimer.cs index 9451b07f3..bb67325d1 100644 --- a/Emby.Server.Implementations/Threading/CommonTimer.cs +++ b/Emby.Server.Implementations/Threading/CommonTimer.cs @@ -31,6 +31,7 @@ namespace Emby.Server.Implementations.Threading public void Dispose() { _timer.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 75328a39a..180463040 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -722,6 +722,7 @@ namespace Emby.Server.Implementations.Updates public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index 98e0c4080..fa1d5b74e 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.UserViews Recursive = recursive, IncludeItemTypes = new[] { typeof(BoxSet).Name }, Limit = 20, - SortBy = new[] { ItemSortBy.Random }, + OrderBy = new [] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, DtoOptions = new DtoOptions(false) }); diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 5b869221a..c27b8ac26 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="Emby.XmlTv" version="1.0.10" targetFramework="net46" /> - <package id="MediaBrowser.Naming" version="1.0.7" targetFramework="net46" /> <package id="ServiceStack.Text" version="4.5.8" targetFramework="net46" /> <package id="SharpCompress" version="0.14.0" targetFramework="net46" /> <package id="SimpleInjector" version="4.0.8" targetFramework="net46" /> |
