diff options
22 files changed, 895 insertions, 850 deletions
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 23b97f70c..067fecd87 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,6 +1,6 @@ #nullable disable -#pragma warning disable CS1591 +#pragma warning disable CS1591, SA1401 using System; using System.Collections.Generic; @@ -41,6 +41,22 @@ namespace MediaBrowser.Controller.Entities public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem> { /// <summary> + /// The trailer folder name. + /// </summary> + public const string TrailerFolderName = "trailers"; + public const string ThemeSongsFolderName = "theme-music"; + public const string ThemeSongFilename = "theme"; + public const string ThemeVideosFolderName = "backdrops"; + public const string ExtrasFolderName = "extras"; + public const string BehindTheScenesFolderName = "behind the scenes"; + public const string DeletedScenesFolderName = "deleted scenes"; + public const string InterviewFolderName = "interviews"; + public const string SceneFolderName = "scenes"; + public const string SampleFolderName = "samples"; + public const string ShortsFolderName = "shorts"; + public const string FeaturettesFolderName = "featurettes"; + + /// <summary> /// The supported image extensions. /// </summary> public static readonly string[] SupportedImageExtensions @@ -61,38 +77,21 @@ namespace MediaBrowser.Controller.Entities ".ttml" }; - protected BaseItem() - { - Tags = Array.Empty<string>(); - Genres = Array.Empty<string>(); - Studios = Array.Empty<string>(); - ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - LockedFields = Array.Empty<MetadataField>(); - ImageInfos = Array.Empty<ItemImageInfo>(); - ProductionLocations = Array.Empty<string>(); - RemoteTrailers = Array.Empty<MediaUrl>(); - ExtraIds = Array.Empty<Guid>(); - } - - public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; - public static char SlugChar = '-'; - /// <summary> - /// The trailer folder name. + /// Extra types that should be counted and displayed as "Special Features" in the UI. /// </summary> - public const string TrailerFolderName = "trailers"; - public const string ThemeSongsFolderName = "theme-music"; - public const string ThemeSongFilename = "theme"; - public const string ThemeVideosFolderName = "backdrops"; - public const string ExtrasFolderName = "extras"; - public const string BehindTheScenesFolderName = "behind the scenes"; - public const string DeletedScenesFolderName = "deleted scenes"; - public const string InterviewFolderName = "interviews"; - public const string SceneFolderName = "scenes"; - public const string SampleFolderName = "samples"; - public const string ShortsFolderName = "shorts"; - public const string FeaturettesFolderName = "featurettes"; + public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType> + { + Model.Entities.ExtraType.Unknown, + Model.Entities.ExtraType.BehindTheScenes, + Model.Entities.ExtraType.Clip, + Model.Entities.ExtraType.DeletedScene, + Model.Entities.ExtraType.Interview, + Model.Entities.ExtraType.Sample, + Model.Entities.ExtraType.Scene + }; + public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; public static readonly string[] AllExtrasTypesFolderNames = { ExtrasFolderName, @@ -105,6 +104,29 @@ namespace MediaBrowser.Controller.Entities FeaturettesFolderName }; + private string _sortName; + private Guid[] _themeSongIds; + private Guid[] _themeVideoIds; + + private string _forcedSortName; + + private string _name; + + public static char SlugChar = '-'; + + protected BaseItem() + { + Tags = Array.Empty<string>(); + Genres = Array.Empty<string>(); + Studios = Array.Empty<string>(); + ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + LockedFields = Array.Empty<MetadataField>(); + ImageInfos = Array.Empty<ItemImageInfo>(); + ProductionLocations = Array.Empty<string>(); + RemoteTrailers = Array.Empty<MediaUrl>(); + ExtraIds = Array.Empty<Guid>(); + } + [JsonIgnore] public Guid[] ThemeSongIds { @@ -194,8 +216,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool SupportsRemoteImageDownloading => true; - private string _name; - /// <summary> /// Gets or sets the name. /// </summary> @@ -328,12 +348,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool IsHidden => false; - public BaseItem GetOwner() - { - var ownerId = OwnerId; - return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId); - } - /// <summary> /// Gets the type of the location. /// </summary> @@ -379,13 +393,6 @@ namespace MediaBrowser.Controller.Entities } } - public bool IsPathProtocol(MediaProtocol protocol) - { - var current = PathProtocol; - - return current.HasValue && current.Value == protocol; - } - [JsonIgnore] public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File); @@ -423,35 +430,17 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool EnableAlphaNumericSorting => true; - private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1) - { - var list = new List<Tuple<StringBuilder, bool>>(); - - int thisMarker = 0; - - while (thisMarker < s1.Length) - { - char thisCh = s1[thisMarker]; + public virtual bool IsHD => Height >= 720; - var thisChunk = new StringBuilder(); - bool isNumeric = char.IsDigit(thisCh); + public bool IsShortcut { get; set; } - while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric) - { - thisChunk.Append(thisCh); - thisMarker++; + public string ShortcutPath { get; set; } - if (thisMarker < s1.Length) - { - thisCh = s1[thisMarker]; - } - } + public int Width { get; set; } - list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric)); - } + public int Height { get; set; } - return list; - } + public Guid[] ExtraIds { get; set; } /// <summary> /// Gets the primary image path. @@ -463,72 +452,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public string PrimaryImagePath => this.GetImagePath(ImageType.Primary); - public virtual bool CanDelete() - { - if (SourceType == SourceType.Channel) - { - return ChannelManager.CanDelete(this); - } - - return IsFileProtocol; - } - - public virtual bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders) - { - if (user.HasPermission(PermissionKind.EnableContentDeletion)) - { - return true; - } - - var allowed = user.GetPreferenceValues<Guid>(PreferenceKind.EnableContentDeletionFromFolders); - - if (SourceType == SourceType.Channel) - { - return allowed.Contains(ChannelId); - } - else - { - var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders); - - foreach (var folder in collectionFolders) - { - if (allowed.Contains(folder.Id)) - { - return true; - } - } - } - - return false; - } - - public bool CanDelete(User user, List<Folder> allCollectionFolders) - { - return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders); - } - - public bool CanDelete(User user) - { - var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); - - return CanDelete(user, allCollectionFolders); - } - - public virtual bool CanDownload() - { - return false; - } - - public virtual bool IsAuthorizedToDownload(User user) - { - return user.HasPermission(PermissionKind.EnableContentDownloading); - } - - public bool CanDownload(User user) - { - return CanDownload() && IsAuthorizedToDownload(user); - } - /// <summary> /// Gets or sets the date created. /// </summary> @@ -548,38 +471,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public DateTime DateLastRefreshed { get; set; } - /// <summary> - /// Gets or sets the logger. - /// </summary> - public static ILogger<BaseItem> Logger { get; set; } - - public static ILibraryManager LibraryManager { get; set; } - - public static IServerConfigurationManager ConfigurationManager { get; set; } - - public static IProviderManager ProviderManager { get; set; } - - public static ILocalizationManager LocalizationManager { get; set; } - - public static IItemRepository ItemRepository { get; set; } - - public static IFileSystem FileSystem { get; set; } - - public static IUserDataManager UserDataManager { get; set; } - - public static IChannelManager ChannelManager { get; set; } - - public static IMediaSourceManager MediaSourceManager { get; set; } - - /// <summary> - /// Returns a <see cref="string" /> that represents this instance. - /// </summary> - /// <returns>A <see cref="string" /> that represents this instance.</returns> - public override string ToString() - { - return Name; - } - [JsonIgnore] public bool IsLocked { get; set; } @@ -611,7 +502,45 @@ namespace MediaBrowser.Controller.Entities } } - private string _forcedSortName; + [JsonIgnore] + public bool EnableMediaSourceDisplay + { + get + { + if (SourceType == SourceType.Channel) + { + return ChannelManager.EnableMediaSourceDisplay(this); + } + + return true; + } + } + + [JsonIgnore] + public Guid ParentId { get; set; } + + /// <summary> + /// Gets or sets the logger. + /// </summary> + public static ILogger<BaseItem> Logger { get; set; } + + public static ILibraryManager LibraryManager { get; set; } + + public static IServerConfigurationManager ConfigurationManager { get; set; } + + public static IProviderManager ProviderManager { get; set; } + + public static ILocalizationManager LocalizationManager { get; set; } + + public static IItemRepository ItemRepository { get; set; } + + public static IFileSystem FileSystem { get; set; } + + public static IUserDataManager UserDataManager { get; set; } + + public static IChannelManager ChannelManager { get; set; } + + public static IMediaSourceManager MediaSourceManager { get; set; } /// <summary> /// Gets or sets the name of the forced sort. @@ -628,10 +557,6 @@ namespace MediaBrowser.Controller.Entities } } - private string _sortName; - private Guid[] _themeSongIds; - private Guid[] _themeVideoIds; - /// <summary> /// Gets or sets the name of the sort. /// </summary> @@ -660,164 +585,6 @@ namespace MediaBrowser.Controller.Entities set => _sortName = value; } - public string GetInternalMetadataPath() - { - var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath; - - return GetInternalMetadataPath(basePath); - } - - protected virtual string GetInternalMetadataPath(string basePath) - { - if (SourceType == SourceType.Channel) - { - return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); - } - - ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture); - - return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString); - } - - /// <summary> - /// Creates the name of the sort. - /// </summary> - /// <returns>System.String.</returns> - protected virtual string CreateSortName() - { - if (Name == null) - { - return null; // some items may not have name filled in properly - } - - if (!EnableAlphaNumericSorting) - { - return Name.TrimStart(); - } - - var sortable = Name.Trim().ToLowerInvariant(); - - foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters) - { - sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal); - } - - foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters) - { - sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal); - } - - foreach (var search in ConfigurationManager.Configuration.SortRemoveWords) - { - // Remove from beginning if a space follows - if (sortable.StartsWith(search + " ", StringComparison.Ordinal)) - { - sortable = sortable.Remove(0, search.Length + 1); - } - - // Remove from middle if surrounded by spaces - sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal); - - // Remove from end if followed by a space - if (sortable.EndsWith(" " + search, StringComparison.Ordinal)) - { - sortable = sortable.Remove(sortable.Length - (search.Length + 1)); - } - } - - return ModifySortChunks(sortable); - } - - private string ModifySortChunks(string name) - { - var chunks = GetSortChunks(name); - - var builder = new StringBuilder(); - - foreach (var chunk in chunks) - { - var chunkBuilder = chunk.Item1; - - // This chunk is numeric - if (chunk.Item2) - { - while (chunkBuilder.Length < 10) - { - chunkBuilder.Insert(0, '0'); - } - } - - builder.Append(chunkBuilder); - } - - // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); - return builder.ToString().RemoveDiacritics(); - } - - [JsonIgnore] - public bool EnableMediaSourceDisplay - { - get - { - if (SourceType == SourceType.Channel) - { - return ChannelManager.EnableMediaSourceDisplay(this); - } - - return true; - } - } - - [JsonIgnore] - public Guid ParentId { get; set; } - - public void SetParent(Folder parent) - { - ParentId = parent == null ? Guid.Empty : parent.Id; - } - - public BaseItem GetParent() - { - var parentId = ParentId; - if (!parentId.Equals(Guid.Empty)) - { - return LibraryManager.GetItemById(parentId); - } - - return null; - } - - public IEnumerable<BaseItem> GetParents() - { - var parent = GetParent(); - - while (parent != null) - { - yield return parent; - - parent = parent.GetParent(); - } - } - - /// <summary> - /// Finds a parent of a given type. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <returns>``0.</returns> - public T FindParent<T>() - where T : Folder - { - foreach (var parent in GetParents()) - { - if (parent is T item) - { - return item; - } - } - - return null; - } - [JsonIgnore] public virtual Guid DisplayParentId { @@ -1001,6 +768,349 @@ namespace MediaBrowser.Controller.Entities } /// <summary> + /// Gets or sets the provider ids. + /// </summary> + /// <value>The provider ids.</value> + [JsonIgnore] + public Dictionary<string, string> ProviderIds { get; set; } + + [JsonIgnore] + public virtual Folder LatestItemsIndexContainer => null; + + [JsonIgnore] + public string PresentationUniqueKey { get; set; } + + [JsonIgnore] + public virtual bool EnableRememberingTrackSelections => true; + + [JsonIgnore] + public virtual bool IsTopParent + { + get + { + if (this is BasePluginFolder || this is Channel) + { + return true; + } + + if (this is IHasCollectionType view) + { + if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + if (GetParent() is AggregateFolder) + { + return true; + } + + return false; + } + } + + [JsonIgnore] + public virtual bool SupportsAncestors => true; + + [JsonIgnore] + public virtual bool StopRefreshIfLocalMetadataFound => true; + + [JsonIgnore] + protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol; + + [JsonIgnore] + public virtual bool SupportsPeople => false; + + [JsonIgnore] + public virtual bool SupportsThemeMedia => false; + + [JsonIgnore] + public virtual bool SupportsInheritedParentImages => false; + + /// <summary> + /// Gets a value indicating whether this instance is folder. + /// </summary> + /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> + [JsonIgnore] + public virtual bool IsFolder => false; + + [JsonIgnore] + public virtual bool IsDisplayedAsFolder => false; + + /// <summary> + /// Gets or sets the remote trailers. + /// </summary> + /// <value>The remote trailers.</value> + public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; } + + public virtual bool SupportsExternalTransfer => false; + + public virtual double GetDefaultPrimaryImageAspectRatio() + { + return 0; + } + + public virtual string CreatePresentationUniqueKey() + { + return Id.ToString("N", CultureInfo.InvariantCulture); + } + + public bool IsPathProtocol(MediaProtocol protocol) + { + var current = PathProtocol; + + return current.HasValue && current.Value == protocol; + } + + private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1) + { + var list = new List<Tuple<StringBuilder, bool>>(); + + int thisMarker = 0; + + while (thisMarker < s1.Length) + { + char thisCh = s1[thisMarker]; + + var thisChunk = new StringBuilder(); + bool isNumeric = char.IsDigit(thisCh); + + while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric) + { + thisChunk.Append(thisCh); + thisMarker++; + + if (thisMarker < s1.Length) + { + thisCh = s1[thisMarker]; + } + } + + list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric)); + } + + return list; + } + + public virtual bool CanDelete() + { + if (SourceType == SourceType.Channel) + { + return ChannelManager.CanDelete(this); + } + + return IsFileProtocol; + } + + public virtual bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders) + { + if (user.HasPermission(PermissionKind.EnableContentDeletion)) + { + return true; + } + + var allowed = user.GetPreferenceValues<Guid>(PreferenceKind.EnableContentDeletionFromFolders); + + if (SourceType == SourceType.Channel) + { + return allowed.Contains(ChannelId); + } + else + { + var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders); + + foreach (var folder in collectionFolders) + { + if (allowed.Contains(folder.Id)) + { + return true; + } + } + } + + return false; + } + + public BaseItem GetOwner() + { + var ownerId = OwnerId; + return ownerId.Equals(Guid.Empty) ? null : LibraryManager.GetItemById(ownerId); + } + + public bool CanDelete(User user, List<Folder> allCollectionFolders) + { + return CanDelete() && IsAuthorizedToDelete(user, allCollectionFolders); + } + + public bool CanDelete(User user) + { + var allCollectionFolders = LibraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); + + return CanDelete(user, allCollectionFolders); + } + + public virtual bool CanDownload() + { + return false; + } + + public virtual bool IsAuthorizedToDownload(User user) + { + return user.HasPermission(PermissionKind.EnableContentDownloading); + } + + public bool CanDownload(User user) + { + return CanDownload() && IsAuthorizedToDownload(user); + } + + /// <summary> + /// Returns a <see cref="string" /> that represents this instance. + /// </summary> + /// <returns>A <see cref="string" /> that represents this instance.</returns> + public override string ToString() + { + return Name; + } + + public string GetInternalMetadataPath() + { + var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath; + + return GetInternalMetadataPath(basePath); + } + + protected virtual string GetInternalMetadataPath(string basePath) + { + if (SourceType == SourceType.Channel) + { + return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); + } + + ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture); + + return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString); + } + + /// <summary> + /// Creates the name of the sort. + /// </summary> + /// <returns>System.String.</returns> + protected virtual string CreateSortName() + { + if (Name == null) + { + return null; // some items may not have name filled in properly + } + + if (!EnableAlphaNumericSorting) + { + return Name.TrimStart(); + } + + var sortable = Name.Trim().ToLowerInvariant(); + + foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters) + { + sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal); + } + + foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters) + { + sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal); + } + + foreach (var search in ConfigurationManager.Configuration.SortRemoveWords) + { + // Remove from beginning if a space follows + if (sortable.StartsWith(search + " ", StringComparison.Ordinal)) + { + sortable = sortable.Remove(0, search.Length + 1); + } + + // Remove from middle if surrounded by spaces + sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal); + + // Remove from end if followed by a space + if (sortable.EndsWith(" " + search, StringComparison.Ordinal)) + { + sortable = sortable.Remove(sortable.Length - (search.Length + 1)); + } + } + + return ModifySortChunks(sortable); + } + + private string ModifySortChunks(string name) + { + var chunks = GetSortChunks(name); + + var builder = new StringBuilder(); + + foreach (var chunk in chunks) + { + var chunkBuilder = chunk.Item1; + + // This chunk is numeric + if (chunk.Item2) + { + while (chunkBuilder.Length < 10) + { + chunkBuilder.Insert(0, '0'); + } + } + + builder.Append(chunkBuilder); + } + + // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); + return builder.ToString().RemoveDiacritics(); + } + + public BaseItem GetParent() + { + var parentId = ParentId; + if (!parentId.Equals(Guid.Empty)) + { + return LibraryManager.GetItemById(parentId); + } + + return null; + } + + public IEnumerable<BaseItem> GetParents() + { + var parent = GetParent(); + + while (parent != null) + { + yield return parent; + + parent = parent.GetParent(); + } + } + + /// <summary> + /// Finds a parent of a given type. + /// </summary> + /// <typeparam name="T">Type of parent.</typeparam> + /// <returns>``0.</returns> + public T FindParent<T>() + where T : Folder + { + foreach (var parent in GetParents()) + { + if (parent is T item) + { + return item; + } + } + + return null; + } + + /// <summary> /// Gets the play access. /// </summary> /// <param name="user">The user.</param> @@ -1405,14 +1515,46 @@ namespace MediaBrowser.Controller.Entities } } - [JsonIgnore] - protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol; + protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) + { + if (!IsVisible(user)) + { + return false; + } - [JsonIgnore] - public virtual bool SupportsPeople => false; + if (GetParents().Any(i => !i.IsVisible(user))) + { + return false; + } - [JsonIgnore] - public virtual bool SupportsThemeMedia => false; + if (checkFolders) + { + var topParent = GetParents().LastOrDefault() ?? this; + + if (string.IsNullOrEmpty(topParent.Path)) + { + return true; + } + + var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList(); + + if (itemCollectionFolders.Count > 0) + { + var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList(); + if (!itemCollectionFolders.Any(userCollectionFolders.Contains)) + { + return false; + } + } + } + + return true; + } + + public void SetParent(Folder parent) + { + ParentId = parent == null ? Guid.Empty : parent.Id; + } /// <summary> /// Refreshes owned items such as trailers, theme videos, special features, etc. @@ -1609,29 +1751,6 @@ namespace MediaBrowser.Controller.Entities return themeSongsChanged; } - /// <summary> - /// Gets or sets the provider ids. - /// </summary> - /// <value>The provider ids.</value> - [JsonIgnore] - public Dictionary<string, string> ProviderIds { get; set; } - - [JsonIgnore] - public virtual Folder LatestItemsIndexContainer => null; - - public virtual double GetDefaultPrimaryImageAspectRatio() - { - return 0; - } - - public virtual string CreatePresentationUniqueKey() - { - return Id.ToString("N", CultureInfo.InvariantCulture); - } - - [JsonIgnore] - public string PresentationUniqueKey { get; set; } - public string GetPresentationUniqueKey() { return PresentationUniqueKey ?? CreatePresentationUniqueKey(); @@ -1929,55 +2048,6 @@ namespace MediaBrowser.Controller.Entities return IsVisibleStandaloneInternal(user, true); } - [JsonIgnore] - public virtual bool SupportsInheritedParentImages => false; - - protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) - { - if (!IsVisible(user)) - { - return false; - } - - if (GetParents().Any(i => !i.IsVisible(user))) - { - return false; - } - - if (checkFolders) - { - var topParent = GetParents().LastOrDefault() ?? this; - - if (string.IsNullOrEmpty(topParent.Path)) - { - return true; - } - - var itemCollectionFolders = LibraryManager.GetCollectionFolders(this).Select(i => i.Id).ToList(); - - if (itemCollectionFolders.Count > 0) - { - var userCollectionFolders = LibraryManager.GetUserRootFolder().GetChildren(user, true).Select(i => i.Id).ToList(); - if (!itemCollectionFolders.Any(userCollectionFolders.Contains)) - { - return false; - } - } - } - - return true; - } - - /// <summary> - /// Gets a value indicating whether this instance is folder. - /// </summary> - /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> - [JsonIgnore] - public virtual bool IsFolder => false; - - [JsonIgnore] - public virtual bool IsDisplayedAsFolder => false; - public virtual string GetClientTypeName() { if (IsFolder && SourceType == SourceType.Channel && !(this is Channel)) @@ -2066,14 +2136,11 @@ namespace MediaBrowser.Controller.Entities return null; } - [JsonIgnore] - public virtual bool EnableRememberingTrackSelections => true; - /// <summary> /// Adds a studio to the item. /// </summary> /// <param name="name">The name.</param> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException">Throws if name is null.</exception> public void AddStudio(string name) { if (string.IsNullOrEmpty(name)) @@ -2109,7 +2176,7 @@ namespace MediaBrowser.Controller.Entities /// Adds a genre to the item. /// </summary> /// <param name="name">The name.</param> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException">Throwns if name is null.</exception> public void AddGenre(string name) { if (string.IsNullOrEmpty(name)) @@ -2132,8 +2199,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="user">The user.</param> /// <param name="datePlayed">The date played.</param> /// <param name="resetPosition">if set to <c>true</c> [reset position].</param> - /// <returns>Task.</returns> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException">Throws if user is null.</exception> public virtual void MarkPlayed( User user, DateTime? datePlayed, @@ -2170,8 +2236,7 @@ namespace MediaBrowser.Controller.Entities /// Marks the unplayed. /// </summary> /// <param name="user">The user.</param> - /// <returns>Task.</returns> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException">Throws if user is null.</exception> public virtual void MarkUnplayed(User user) { if (user == null) @@ -2271,6 +2336,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="type">The type.</param> /// <param name="index">The index.</param> + /// <returns>A task.</returns> public async Task DeleteImageAsync(ImageType type, int index) { var info = GetImageInfo(type, index); @@ -2308,6 +2374,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Validates that images within the item are still on the filesystem. /// </summary> + /// <param name="directoryService">The directory service to use.</param> + /// <returns><c>true</c> if the images validate, <c>false</c> if not.</returns> public bool ValidateImages(IDirectoryService directoryService) { var allFiles = ImageInfos @@ -2335,7 +2403,6 @@ namespace MediaBrowser.Controller.Entities /// <param name="imageType">Type of the image.</param> /// <param name="imageIndex">Index of the image.</param> /// <returns>System.String.</returns> - /// <exception cref="InvalidOperationException"> </exception> /// <exception cref="ArgumentNullException">Item is null.</exception> public string GetImagePath(ImageType imageType, int imageIndex) => GetImageInfo(imageType, imageIndex)?.Path; @@ -2821,39 +2888,6 @@ namespace MediaBrowser.Controller.Entities return GetParents().FirstOrDefault(parent => parent.IsTopParent); } - [JsonIgnore] - public virtual bool IsTopParent - { - get - { - if (this is BasePluginFolder || this is Channel) - { - return true; - } - - if (this is IHasCollectionType view) - { - if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - if (GetParent() is AggregateFolder) - { - return true; - } - - return false; - } - } - - [JsonIgnore] - public virtual bool SupportsAncestors => true; - - [JsonIgnore] - public virtual bool StopRefreshIfLocalMetadataFound => true; - public virtual IEnumerable<Guid> GetIdsForAncestorQuery() { return new[] { Id }; @@ -2888,6 +2922,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Updates the official rating based on content and returns true or false indicating if it changed. /// </summary> + /// <param name="children">Media children.</param> /// <returns><c>true</c> if the rating was updated; otherwise <c>false</c>.</returns> public bool UpdateRatingToItems(IList<BaseItem> children) { @@ -2921,12 +2956,6 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Gets or sets the remote trailers. - /// </summary> - /// <value>The remote trailers.</value> - public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; } - - /// <summary> /// Get all extras associated with this item, sorted by <see cref="SortName"/>. /// </summary> /// <returns>An enumerable containing the items.</returns> @@ -2963,39 +2992,11 @@ namespace MediaBrowser.Controller.Entities } } - public virtual bool IsHD => Height >= 720; - - public bool IsShortcut { get; set; } - - public string ShortcutPath { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public Guid[] ExtraIds { get; set; } - public virtual long GetRunTimeTicksForPlayState() { return RunTimeTicks ?? 0; } - /// <summary> - /// Extra types that should be counted and displayed as "Special Features" in the UI. - /// </summary> - public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType> - { - Model.Entities.ExtraType.Unknown, - Model.Entities.ExtraType.BehindTheScenes, - Model.Entities.ExtraType.Clip, - Model.Entities.ExtraType.DeletedScene, - Model.Entities.ExtraType.Interview, - Model.Entities.ExtraType.Sample, - Model.Entities.ExtraType.Scene - }; - - public virtual bool SupportsExternalTransfer => false; - /// <inheritdoc /> public override bool Equals(object obj) { diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index 89ad392a4..e88121212 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="source">The source object.</param> /// <param name="dest">The destination object.</param> + /// <typeparam name="T">Source type.</typeparam> + /// <typeparam name="TU">Destination type.</typeparam> public static void DeepCopy<T, TU>(this T source, TU dest) where T : BaseItem where TU : BaseItem @@ -109,6 +111,9 @@ namespace MediaBrowser.Controller.Entities /// Copies all properties on newly created object. Skips properties that do not exist. /// </summary> /// <param name="source">The source object.</param> + /// <typeparam name="T">Source type.</typeparam> + /// <typeparam name="TU">Destination type.</typeparam> + /// <returns>Destination object.</returns> public static TU DeepCopy<T, TU>(this T source) where T : BaseItem where TU : BaseItem, new() diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index 1bd25042f..272a37df1 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual string CollectionType => null; + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override bool SupportsPeople => false; + public override bool CanDelete() { return false; @@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Entities { return true; } - - [JsonIgnore] - public override bool SupportsInheritedParentImages => false; - - [JsonIgnore] - public override bool SupportsPeople => false; } } diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 4f367fe2b..0fb4771dd 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -41,6 +41,23 @@ namespace MediaBrowser.Controller.Entities PhysicalFolderIds = Array.Empty<Guid>(); } + /// <summary> + /// Gets the display preferences id. + /// </summary> + /// <remarks> + /// Allow different display preferences for each collection folder. + /// </remarks> + /// <value>The display prefs id.</value> + [JsonIgnore] + public override Guid DisplayPreferencesId => Id; + + [JsonIgnore] + public override string[] PhysicalLocations => PhysicalLocationsList; + + public string[] PhysicalLocationsList { get; set; } + + public Guid[] PhysicalFolderIds { get; set; } + public static IXmlSerializer XmlSerializer { get; set; } public static IServerApplicationHost ApplicationHost { get; set; } @@ -63,6 +80,9 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override IEnumerable<BaseItem> Children => GetActualChildren(); + [JsonIgnore] + public override bool SupportsPeople => false; + public override bool CanDelete() { return false; @@ -160,23 +180,6 @@ namespace MediaBrowser.Controller.Entities } } - /// <summary> - /// Gets the display preferences id. - /// </summary> - /// <remarks> - /// Allow different display preferences for each collection folder. - /// </remarks> - /// <value>The display prefs id.</value> - [JsonIgnore] - public override Guid DisplayPreferencesId => Id; - - [JsonIgnore] - public override string[] PhysicalLocations => PhysicalLocationsList; - - public string[] PhysicalLocationsList { get; set; } - - public Guid[] PhysicalFolderIds { get; set; } - public override bool IsSaveLocalMetadataEnabled() { return true; @@ -373,8 +376,5 @@ namespace MediaBrowser.Controller.Entities return result; } - - [JsonIgnore] - public override bool SupportsPeople => false; } } diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index d8bc0069c..9ce8eebe3 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Adds the trailer URL. /// </summary> + /// <param name="item">Media item.</param> + /// <param name="url">Trailer URL.</param> public static void AddTrailerUrl(this BaseItem item, string url) { if (string.IsNullOrEmpty(url)) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 34ad5bbbb..d45a02cf2 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1669,7 +1669,6 @@ namespace MediaBrowser.Controller.Entities /// <param name="user">The user.</param> /// <param name="datePlayed">The date played.</param> /// <param name="resetPosition">if set to <c>true</c> [reset position].</param> - /// <returns>Task.</returns> public override void MarkPlayed( User user, DateTime? datePlayed, @@ -1711,7 +1710,6 @@ namespace MediaBrowser.Controller.Entities /// Marks the unplayed. /// </summary> /// <param name="user">The user.</param> - /// <returns>Task.</returns> public override void MarkUnplayed(User user) { var itemsResult = GetItemList(new InternalItemsQuery diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs index 98c3b3edf..b11dac381 100644 --- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs +++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs @@ -20,6 +20,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets the media sources. /// </summary> + /// <param name="enablePathSubstitution"><c>true</c> to enable path substitution, <c>false</c> to not.</param> + /// <returns>A lits of media sources.</returns> List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution); List<MediaStream> GetMediaStreams(); diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs index 2bd9ded33..f4271678d 100644 --- a/MediaBrowser.Controller/Entities/IHasTrailers.cs +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -39,6 +39,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets the trailer count. /// </summary> + /// <param name="item">Media item.</param> /// <returns><see cref="IReadOnlyList{Guid}" />.</returns> public static int GetTrailerCount(this IHasTrailers item) => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count; @@ -46,6 +47,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets the trailer ids. /// </summary> + /// <param name="item">Media item.</param> /// <returns><see cref="IReadOnlyList{Guid}" />.</returns> public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item) { @@ -70,6 +72,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets the trailers. /// </summary> + /// <param name="item">Media item.</param> /// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns> public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item) { diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index b9e37269e..045c1b89f 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -129,6 +129,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// </summary> + /// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param> + /// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns> public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 556624e14..c8feb1c94 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -105,6 +105,8 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// </summary> + /// <param name="replaceAllMetadata"><c>true</c> to replace all metadata, <c>false</c> to not.</param> + /// <returns><c>true</c> if changes were made, <c>false</c> if not.</returns> public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 31c179bca..27c3ff81b 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -49,12 +49,6 @@ namespace MediaBrowser.Controller.Entities.TV /// <value>The index number.</value> public int? IndexNumberEnd { get; set; } - public string FindSeriesSortName() - { - var series = Series; - return series == null ? SeriesName : series.SortName; - } - [JsonIgnore] protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1; @@ -76,45 +70,6 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] protected override bool EnableDefaultVideoUserDataKeys => false; - public override double GetDefaultPrimaryImageAspectRatio() - { - // hack for tv plugins - if (SourceType == SourceType.Channel) - { - return 0; - } - - return 16.0 / 9; - } - - public override List<string> GetUserDataKeys() - { - var list = base.GetUserDataKeys(); - - var series = Series; - if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) - { - var seriesUserDataKeys = series.GetUserDataKeys(); - var take = seriesUserDataKeys.Count; - if (seriesUserDataKeys.Count > 1) - { - take--; - } - - var newList = seriesUserDataKeys.GetRange(0, take); - var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture); - for (int i = 0; i < take; i++) - { - newList[i] = newList[i] + suffix; - } - - newList.AddRange(list); - list = newList; - } - - return list; - } - /// <summary> /// Gets the Episode's Series Instance. /// </summary> @@ -161,6 +116,74 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] public string SeasonName { get; set; } + [JsonIgnore] + public override bool SupportsRemoteImageDownloading + { + get + { + if (IsMissingEpisode) + { + return false; + } + + return true; + } + } + + [JsonIgnore] + public bool IsMissingEpisode => LocationType == LocationType.Virtual; + + [JsonIgnore] + public Guid SeasonId { get; set; } + + [JsonIgnore] + public Guid SeriesId { get; set; } + + public string FindSeriesSortName() + { + var series = Series; + return series == null ? SeriesName : series.SortName; + } + + public override double GetDefaultPrimaryImageAspectRatio() + { + // hack for tv plugins + if (SourceType == SourceType.Channel) + { + return 0; + } + + return 16.0 / 9; + } + + public override List<string> GetUserDataKeys() + { + var list = base.GetUserDataKeys(); + + var series = Series; + if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) + { + var seriesUserDataKeys = series.GetUserDataKeys(); + var take = seriesUserDataKeys.Count; + if (seriesUserDataKeys.Count > 1) + { + take--; + } + + var newList = seriesUserDataKeys.GetRange(0, take); + var suffix = ParentIndexNumber.Value.ToString("000", CultureInfo.InvariantCulture) + IndexNumber.Value.ToString("000", CultureInfo.InvariantCulture); + for (int i = 0; i < take; i++) + { + newList[i] = newList[i] + suffix; + } + + newList.AddRange(list); + list = newList; + } + + return list; + } + public string FindSeriesPresentationUniqueKey() { var series = Series; @@ -242,29 +265,6 @@ namespace MediaBrowser.Controller.Entities.TV return false; } - [JsonIgnore] - public override bool SupportsRemoteImageDownloading - { - get - { - if (IsMissingEpisode) - { - return false; - } - - return true; - } - } - - [JsonIgnore] - public bool IsMissingEpisode => LocationType == LocationType.Virtual; - - [JsonIgnore] - public Guid SeasonId { get; set; } - - [JsonIgnore] - public Guid SeriesId { get; set; } - public Guid FindSeriesId() { var series = FindParent<Series>(); diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index aa62bb35b..926c7b045 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -38,6 +38,50 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] public override Guid DisplayParentId => SeriesId; + /// <summary> + /// Gets this Episode's Series Instance. + /// </summary> + /// <value>The series.</value> + [JsonIgnore] + public Series Series + { + get + { + var seriesId = SeriesId; + if (seriesId == Guid.Empty) + { + seriesId = FindSeriesId(); + } + + return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series); + } + } + + [JsonIgnore] + public string SeriesPath + { + get + { + var series = Series; + + if (series != null) + { + return series.Path; + } + + return System.IO.Path.GetDirectoryName(Path); + } + } + + [JsonIgnore] + public string SeriesPresentationUniqueKey { get; set; } + + [JsonIgnore] + public string SeriesName { get; set; } + + [JsonIgnore] + public Guid SeriesId { get; set; } + public override double GetDefaultPrimaryImageAspectRatio() { double value = 2; @@ -80,41 +124,6 @@ namespace MediaBrowser.Controller.Entities.TV return result; } - /// <summary> - /// Gets this Episode's Series Instance. - /// </summary> - /// <value>The series.</value> - [JsonIgnore] - public Series Series - { - get - { - var seriesId = SeriesId; - if (seriesId == Guid.Empty) - { - seriesId = FindSeriesId(); - } - - return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series); - } - } - - [JsonIgnore] - public string SeriesPath - { - get - { - var series = Series; - - if (series != null) - { - return series.Path; - } - - return System.IO.Path.GetDirectoryName(Path); - } - } - public override string CreatePresentationUniqueKey() { if (IndexNumber.HasValue) @@ -157,6 +166,9 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Gets the episodes. /// </summary> + /// <param name="user">The user.</param> + /// <param name="options">The options to use.</param> + /// <returns>Set of episodes.</returns> public List<BaseItem> GetEpisodes(User user, DtoOptions options) { return GetEpisodes(Series, user, options); @@ -193,15 +205,6 @@ namespace MediaBrowser.Controller.Entities.TV return UnratedItem.Series; } - [JsonIgnore] - public string SeriesPresentationUniqueKey { get; set; } - - [JsonIgnore] - public string SeriesName { get; set; } - - [JsonIgnore] - public Guid SeriesId { get; set; } - public string FindSeriesPresentationUniqueKey() { var series = Series; @@ -241,6 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// </summary> + /// <param name="replaceAllMetadata"><c>true</c> to replace metdata, <c>false</c> to not.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 44d07b4a4..beda504b9 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -72,6 +72,9 @@ namespace MediaBrowser.Controller.Entities.TV /// <value>The status.</value> public SeriesStatus? Status { get; set; } + [JsonIgnore] + public override bool StopRefreshIfLocalMetadataFound => false; + public override double GetDefaultPrimaryImageAspectRatio() { double value = 2; @@ -394,6 +397,10 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Filters the episodes by season. /// </summary> + /// <param name="episodes">The episodes.</param> + /// <param name="parentSeason">The season.</param> + /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param> + /// <returns>The set of episodes.</returns> public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, bool includeSpecials) { var seasonNumber = parentSeason.IndexNumber; @@ -424,6 +431,10 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Filters the episodes by season. /// </summary> + /// <param name="episodes">The episodes.</param> + /// <param name="seasonNumber">The season.</param> + /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param> + /// <returns>The set of episodes.</returns> public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials) { if (!includeSpecials || seasonNumber < 1) @@ -499,8 +510,5 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - - [JsonIgnore] - public override bool StopRefreshIfLocalMetadataFound => false; } } diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index 6ab2116d7..9179eae93 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -13,6 +13,13 @@ namespace MediaBrowser.Controller.Entities public class UserItemData { /// <summary> + /// The _rating. + /// </summary> + private double? _rating; + + public const double MinLikeValue = 6.5; + + /// <summary> /// Gets or sets the user id. /// </summary> /// <value>The user id.</value> @@ -25,11 +32,6 @@ namespace MediaBrowser.Controller.Entities public string Key { get; set; } /// <summary> - /// The _rating. - /// </summary> - private double? _rating; - - /// <summary> /// Gets or sets the users 0-10 rating. /// </summary> /// <value>The rating.</value> @@ -93,8 +95,6 @@ namespace MediaBrowser.Controller.Entities /// <value>The index of the subtitle stream.</value> public int? SubtitleStreamIndex { get; set; } - public const double MinLikeValue = 6.5; - /// <summary> /// Gets or sets a value indicating whether the item is liked or not. /// This should never be serialized. diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 2b15a52f0..f3bf4749d 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -21,8 +21,28 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class UserRootFolder : Folder { - private List<Guid> _childrenIds = null; private readonly object _childIdsLock = new object(); + private List<Guid> _childrenIds = null; + + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override bool SupportsPlayedStatus => false; + + [JsonIgnore] + protected override bool SupportsShortcutChildren => true; + + [JsonIgnore] + public override bool IsPreSorted => true; + + private void ClearCache() + { + lock (_childIdsLock) + { + _childrenIds = null; + } + } protected override List<BaseItem> LoadChildren() { @@ -39,20 +59,6 @@ namespace MediaBrowser.Controller.Entities } } - [JsonIgnore] - public override bool SupportsInheritedParentImages => false; - - [JsonIgnore] - public override bool SupportsPlayedStatus => false; - - private void ClearCache() - { - lock (_childIdsLock) - { - _childrenIds = null; - } - } - protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query) { if (query.Recursive) @@ -74,12 +80,6 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, true).Count; } - [JsonIgnore] - protected override bool SupportsShortcutChildren => true; - - [JsonIgnore] - public override bool IsPreSorted => true; - protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) { var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList(); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index d05b5df2f..7dd95b85c 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -28,6 +28,14 @@ namespace MediaBrowser.Controller.Entities ISupportsPlaceHolders, IHasMediaSources { + public Video() + { + AdditionalParts = Array.Empty<string>(); + LocalAlternateVersions = Array.Empty<string>(); + SubtitleFiles = Array.Empty<string>(); + LinkedAlternateVersions = Array.Empty<LinkedChild>(); + } + [JsonIgnore] public string PrimaryVersionId { get; set; } @@ -74,30 +82,6 @@ namespace MediaBrowser.Controller.Entities } } - public void SetPrimaryVersionId(string id) - { - if (string.IsNullOrEmpty(id)) - { - PrimaryVersionId = null; - } - else - { - PrimaryVersionId = id; - } - - PresentationUniqueKey = CreatePresentationUniqueKey(); - } - - public override string CreatePresentationUniqueKey() - { - if (!string.IsNullOrEmpty(PrimaryVersionId)) - { - return PrimaryVersionId; - } - - return base.CreatePresentationUniqueKey(); - } - [JsonIgnore] public override bool SupportsThemeMedia => true; @@ -151,24 +135,6 @@ namespace MediaBrowser.Controller.Entities /// <value>The aspect ratio.</value> public string AspectRatio { get; set; } - public Video() - { - AdditionalParts = Array.Empty<string>(); - LocalAlternateVersions = Array.Empty<string>(); - SubtitleFiles = Array.Empty<string>(); - LinkedAlternateVersions = Array.Empty<LinkedChild>(); - } - - public override bool CanDownload() - { - if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay) - { - return false; - } - - return IsFileProtocol; - } - [JsonIgnore] public override bool SupportsAddingToPlaylist => true; @@ -196,16 +162,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0; - public IEnumerable<Guid> GetAdditionalPartIds() - { - return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); - } - - public IEnumerable<Guid> GetLocalAlternateVersionIds() - { - return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); - } - public static ILiveTvManager LiveTvManager { get; set; } [JsonIgnore] @@ -222,37 +178,77 @@ namespace MediaBrowser.Controller.Entities } } - protected override bool IsActiveRecording() + [JsonIgnore] + public bool IsCompleteMedia { - return LiveTvManager.GetActiveRecordingInfo(Path) != null; + get + { + if (SourceType == SourceType.Channel) + { + return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase); + } + + return !IsActiveRecording(); + } } - public override bool CanDelete() + [JsonIgnore] + protected virtual bool EnableDefaultVideoUserDataKeys => true; + + [JsonIgnore] + public override string ContainingFolderPath { - if (IsActiveRecording()) + get { - return false; - } + if (IsStacked) + { + return System.IO.Path.GetDirectoryName(Path); + } - return base.CanDelete(); + if (!IsPlaceHolder) + { + if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) + { + return Path; + } + } + + return base.ContainingFolderPath; + } } [JsonIgnore] - public bool IsCompleteMedia + public override string FileNameWithoutExtension { get { - if (SourceType == SourceType.Channel) + if (IsFileProtocol) { - return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase); + if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) + { + return System.IO.Path.GetFileName(Path); + } + + return System.IO.Path.GetFileNameWithoutExtension(Path); } - return !IsActiveRecording(); + return null; } } + /// <summary> + /// Gets a value indicating whether [is3 D]. + /// </summary> + /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value> [JsonIgnore] - protected virtual bool EnableDefaultVideoUserDataKeys => true; + public bool Is3D => Video3DFormat.HasValue; + + /// <summary> + /// Gets the type of the media. + /// </summary> + /// <value>The type of the media.</value> + [JsonIgnore] + public override string MediaType => Model.Entities.MediaType.Video; public override List<string> GetUserDataKeys() { @@ -293,6 +289,65 @@ namespace MediaBrowser.Controller.Entities return list; } + public void SetPrimaryVersionId(string id) + { + if (string.IsNullOrEmpty(id)) + { + PrimaryVersionId = null; + } + else + { + PrimaryVersionId = id; + } + + PresentationUniqueKey = CreatePresentationUniqueKey(); + } + + public override string CreatePresentationUniqueKey() + { + if (!string.IsNullOrEmpty(PrimaryVersionId)) + { + return PrimaryVersionId; + } + + return base.CreatePresentationUniqueKey(); + } + + public override bool CanDownload() + { + if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay) + { + return false; + } + + return IsFileProtocol; + } + + protected override bool IsActiveRecording() + { + return LiveTvManager.GetActiveRecordingInfo(Path) != null; + } + + public override bool CanDelete() + { + if (IsActiveRecording()) + { + return false; + } + + return base.CanDelete(); + } + + public IEnumerable<Guid> GetAdditionalPartIds() + { + return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); + } + + public IEnumerable<Guid> GetLocalAlternateVersionIds() + { + return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video))); + } + private string GetUserDataKey(string providerId) { var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant(); @@ -328,47 +383,6 @@ namespace MediaBrowser.Controller.Entities .OrderBy(i => i.SortName); } - [JsonIgnore] - public override string ContainingFolderPath - { - get - { - if (IsStacked) - { - return System.IO.Path.GetDirectoryName(Path); - } - - if (!IsPlaceHolder) - { - if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) - { - return Path; - } - } - - return base.ContainingFolderPath; - } - } - - [JsonIgnore] - public override string FileNameWithoutExtension - { - get - { - if (IsFileProtocol) - { - if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) - { - return System.IO.Path.GetFileName(Path); - } - - return System.IO.Path.GetFileNameWithoutExtension(Path); - } - - return null; - } - } - internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem) { var updateType = base.UpdateFromResolvedItem(newItem); @@ -397,20 +411,6 @@ namespace MediaBrowser.Controller.Entities return updateType; } - /// <summary> - /// Gets a value indicating whether [is3 D]. - /// </summary> - /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value> - [JsonIgnore] - public bool Is3D => Video3DFormat.HasValue; - - /// <summary> - /// Gets the type of the media. - /// </summary> - /// <value>The type of the media.</value> - [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Video; - protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index d8995ce74..0813a8e7d 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -1,6 +1,6 @@ #nullable disable -#pragma warning disable CS1591 +#pragma warning disable CS1591, SA1306, SA1401 using System; using System.Collections.Generic; @@ -31,6 +31,21 @@ namespace MediaBrowser.Controller.Net new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>(); /// <summary> + /// The logger. + /// </summary> + protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger; + + protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + Logger = logger; + } + + /// <summary> /// Gets the type used for the messages sent to the client. /// </summary> /// <value>The type.</value> @@ -55,21 +70,6 @@ namespace MediaBrowser.Controller.Net protected abstract Task<TReturnDataType> GetDataToSend(); /// <summary> - /// The logger. - /// </summary> - protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger; - - protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger) - { - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - Logger = logger; - } - - /// <summary> /// Processes the message. /// </summary> /// <param name="message">The message.</param> diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 5fa5834c8..c43acfb6d 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -18,7 +18,6 @@ namespace MediaBrowser.Controller.Persistence /// <param name="key">The key.</param> /// <param name="userData">The user data.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken); /// <summary> diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 3eaf23515..5e671a725 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -31,24 +31,18 @@ namespace MediaBrowser.Controller.Playlists ".zpl" }; - public Guid OwnerUserId { get; set; } - - public Share[] Shares { get; set; } - public Playlist() { Shares = Array.Empty<Share>(); } + public Guid OwnerUserId { get; set; } + + public Share[] Shares { get; set; } + [JsonIgnore] public bool IsFile => IsPlaylistFile(Path); - public static bool IsPlaylistFile(string path) - { - // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot). - return System.IO.Path.HasExtension(path) && !Directory.Exists(path); - } - [JsonIgnore] public override string ContainingFolderPath { @@ -80,6 +74,41 @@ namespace MediaBrowser.Controller.Playlists [JsonIgnore] public override bool SupportsCumulativeRunTimeTicks => true; + [JsonIgnore] + public override bool IsPreSorted => true; + + public string PlaylistMediaType { get; set; } + + [JsonIgnore] + public override string MediaType => PlaylistMediaType; + + [JsonIgnore] + private bool IsSharedItem + { + get + { + var path = Path; + + if (string.IsNullOrEmpty(path)) + { + return false; + } + + return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path); + } + } + + public static bool IsPlaylistFile(string path) + { + // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot). + return System.IO.Path.HasExtension(path) && !Directory.Exists(path); + } + + public void SetMediaType(string value) + { + PlaylistMediaType = value; + } + public override double GetDefaultPrimaryImageAspectRatio() { return 1; @@ -197,35 +226,6 @@ namespace MediaBrowser.Controller.Playlists return new[] { item }; } - [JsonIgnore] - public override bool IsPreSorted => true; - - public string PlaylistMediaType { get; set; } - - [JsonIgnore] - public override string MediaType => PlaylistMediaType; - - public void SetMediaType(string value) - { - PlaylistMediaType = value; - } - - [JsonIgnore] - private bool IsSharedItem - { - get - { - var path = Path; - - if (string.IsNullOrEmpty(path)) - { - return false; - } - - return FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, path); - } - } - public override bool IsVisible(User user) { if (!IsSharedItem) diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs index 75286eadc..b95d00aa3 100644 --- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs @@ -14,17 +14,17 @@ namespace MediaBrowser.Controller.Resolvers public interface IItemResolver { /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + ResolverPriority Priority { get; } + + /// <summary> /// Resolves the path. /// </summary> /// <param name="args">The args.</param> /// <returns>BaseItem.</returns> BaseItem ResolvePath(ItemResolveArgs args); - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - ResolverPriority Priority { get; } } public interface IMultiItemResolver @@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Resolvers public class MultiItemResolverResult { - public List<BaseItem> Items { get; set; } - - public List<FileSystemMetadata> ExtraFiles { get; set; } - public MultiItemResolverResult() { Items = new List<BaseItem>(); ExtraFiles = new List<FileSystemMetadata>(); } + + public List<BaseItem> Items { get; set; } + + public List<FileSystemMetadata> ExtraFiles { get; set; } } } diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 9e661cbe4..3330dd540 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Subtitles /// <summary> /// Searches the subtitles. /// </summary> + /// <param name="video">The video.</param> + /// <param name="language">Subtitle language.</param> + /// <param name="isPerfectMatch">Require perfect match.</param> + /// <param name="cancellationToken">CancellationToken to use for the operation.</param> + /// <returns>Subtitles, wrapped in task.</returns> Task<RemoteSubtitleInfo[]> SearchSubtitles( Video video, string language, @@ -47,11 +52,20 @@ namespace MediaBrowser.Controller.Subtitles /// <summary> /// Downloads the subtitles. /// </summary> + /// <param name="video">The video.</param> + /// <param name="subtitleId">Subtitle ID.</param> + /// <param name="cancellationToken">CancellationToken to use for the operation.</param> + /// <returns>A task.</returns> Task DownloadSubtitles(Video video, string subtitleId, CancellationToken cancellationToken); /// <summary> /// Downloads the subtitles. /// </summary> + /// <param name="video">The video.</param> + /// <param name="libraryOptions">Library options to use.</param> + /// <param name="subtitleId">Subtitle ID.</param> + /// <param name="cancellationToken">CancellationToken to use for the operation.</param> + /// <returns>A task.</returns> Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken); /// <summary> @@ -73,11 +87,16 @@ namespace MediaBrowser.Controller.Subtitles /// <summary> /// Deletes the subtitles. /// </summary> + /// <param name="item">Media item.</param> + /// <param name="index">Subtitle index.</param> + /// <returns>A task.</returns> Task DeleteSubtitles(BaseItem item, int index); /// <summary> /// Gets the providers. /// </summary> + /// <param name="item">The media item.</param> + /// <returns>Subtitles providers.</returns> SubtitleProviderInfo[] GetSupportedProviders(BaseItem item); } } diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs index 0f7c47e76..767d87d46 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs @@ -11,6 +11,15 @@ namespace MediaBrowser.Controller.Subtitles { public class SubtitleSearchRequest : IHasProviderIds { + public SubtitleSearchRequest() + { + SearchAllProviders = true; + ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + DisabledSubtitleFetchers = Array.Empty<string>(); + SubtitleFetcherOrder = Array.Empty<string>(); + } + public string Language { get; set; } public string TwoLetterISOLanguageName { get; set; } @@ -42,14 +51,5 @@ namespace MediaBrowser.Controller.Subtitles public string[] DisabledSubtitleFetchers { get; set; } public string[] SubtitleFetcherOrder { get; set; } - - public SubtitleSearchRequest() - { - SearchAllProviders = true; - ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - DisabledSubtitleFetchers = Array.Empty<string>(); - SubtitleFetcherOrder = Array.Empty<string>(); - } } } |
