diff options
Diffstat (limited to 'MediaBrowser.LocalMetadata')
5 files changed, 257 insertions, 233 deletions
diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index f56bc71d0d..599c6cd64f 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.LocalMetadata.Images if (item.SupportsLocalMetadata) { // Episode has its own provider - if (item is Episode || item is Audio || item is Photo) + if (item is Episode || (item is Audio && item is not AudioBook) || item is Photo) { return false; } diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 119effe791..340d9843ff 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -143,16 +143,16 @@ namespace MediaBrowser.LocalMetadata.Parsers item.Name = reader.ReadNormalizedString(); break; case "CriticRating": - { - var text = reader.ReadElementContentAsString(); - - if (float.TryParse(text, CultureInfo.InvariantCulture, out var value)) { - item.CriticRating = value; - } + var text = reader.ReadElementContentAsString(); - break; - } + if (float.TryParse(text, CultureInfo.InvariantCulture, out var value)) + { + item.CriticRating = value; + } + + break; + } case "SortTitle": item.ForcedSortName = reader.ReadNormalizedString(); @@ -176,55 +176,55 @@ namespace MediaBrowser.LocalMetadata.Parsers break; case "LockedFields": - { - var val = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(val)) { - item.LockedFields = val.Split('|').Select(i => + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) { - if (Enum.TryParse(i, true, out MetadataField field)) + item.LockedFields = val.Split('|').Select(i => { - return (MetadataField?)field; - } + if (Enum.TryParse(i, true, out MetadataField field)) + { + return (MetadataField?)field; + } - return null; - }).Where(i => i.HasValue).Select(i => i!.Value).ToArray(); - } + return null; + }).Where(i => i.HasValue).Select(i => i!.Value).ToArray(); + } - break; - } + break; + } case "TagLines": - { - if (!reader.IsEmptyElement) { - using (var subtree = reader.ReadSubtree()) + if (!reader.IsEmptyElement) { - FetchFromTaglinesNode(subtree, item); + using (var subtree = reader.ReadSubtree()) + { + FetchFromTaglinesNode(subtree, item); + } + } + else + { + reader.Read(); } - } - else - { - reader.Read(); - } - break; - } + break; + } case "Countries": - { - if (!reader.IsEmptyElement) - { - reader.Skip(); - } - else { - reader.Read(); - } + if (!reader.IsEmptyElement) + { + reader.Skip(); + } + else + { + reader.Read(); + } - break; - } + break; + } case "ContentRating": case "MPAARating": @@ -307,19 +307,19 @@ namespace MediaBrowser.LocalMetadata.Parsers break; case "Trailers": - { - if (!reader.IsEmptyElement) - { - using var subtree = reader.ReadSubtree(); - FetchDataFromTrailersNode(subtree, item); - } - else { - reader.Read(); - } + if (!reader.IsEmptyElement) + { + using var subtree = reader.ReadSubtree(); + FetchDataFromTrailersNode(subtree, item); + } + else + { + reader.Read(); + } - break; - } + break; + } case "ProductionYear": if (reader.TryReadInt(out var productionYear) && productionYear > 1850) @@ -330,20 +330,20 @@ namespace MediaBrowser.LocalMetadata.Parsers break; case "Rating": case "IMDBrating": - { - var rating = reader.ReadNormalizedString(); - - if (!string.IsNullOrEmpty(rating)) { - // All external meta is saving this as '.' for decimal I believe...but just to be sure - if (float.TryParse(rating.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var val)) + var rating = reader.ReadNormalizedString(); + + if (!string.IsNullOrEmpty(rating)) { - item.CommunityRating = val; + // All external meta is saving this as '.' for decimal I believe...but just to be sure + if (float.TryParse(rating.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var val)) + { + item.CommunityRating = val; + } } - } - break; - } + break; + } case "BirthDate": case "PremiereDate": @@ -370,144 +370,144 @@ namespace MediaBrowser.LocalMetadata.Parsers break; case "Genres": - { - if (!reader.IsEmptyElement) - { - using var subtree = reader.ReadSubtree(); - FetchFromGenresNode(subtree, item); - } - else { - reader.Read(); - } + if (!reader.IsEmptyElement) + { + using var subtree = reader.ReadSubtree(); + FetchFromGenresNode(subtree, item); + } + else + { + reader.Read(); + } - break; - } + break; + } case "Tags": - { - if (!reader.IsEmptyElement) - { - using var subtree = reader.ReadSubtree(); - FetchFromTagsNode(subtree, item); - } - else { - reader.Read(); - } + if (!reader.IsEmptyElement) + { + using var subtree = reader.ReadSubtree(); + FetchFromTagsNode(subtree, item); + } + else + { + reader.Read(); + } - break; - } + break; + } case "Persons": - { - if (!reader.IsEmptyElement) - { - using var subtree = reader.ReadSubtree(); - FetchDataFromPersonsNode(subtree, itemResult); - } - else { - reader.Read(); - } + if (!reader.IsEmptyElement) + { + using var subtree = reader.ReadSubtree(); + FetchDataFromPersonsNode(subtree, itemResult); + } + else + { + reader.Read(); + } - break; - } + break; + } case "Studios": - { - if (!reader.IsEmptyElement) - { - using var subtree = reader.ReadSubtree(); - FetchFromStudiosNode(subtree, item); - } - else { - reader.Read(); - } + if (!reader.IsEmptyElement) + { + using var subtree = reader.ReadSubtree(); + FetchFromStudiosNode(subtree, item); + } + else + { + reader.Read(); + } - break; - } + break; + } case "Shares": - { - if (!reader.IsEmptyElement) { - using var subtree = reader.ReadSubtree(); - if (item is IHasShares hasShares) + if (!reader.IsEmptyElement) { - FetchFromSharesNode(subtree, hasShares); + using var subtree = reader.ReadSubtree(); + if (item is IHasShares hasShares) + { + FetchFromSharesNode(subtree, hasShares); + } + } + else + { + reader.Read(); } - } - else - { - reader.Read(); - } - break; - } + break; + } case "OwnerUserId": - { - var val = reader.ReadNormalizedString(); - - if (Guid.TryParse(val, out var guid) && !guid.Equals(Guid.Empty)) { - if (item is Playlist playlist) + var val = reader.ReadNormalizedString(); + + if (Guid.TryParse(val, out var guid) && !guid.Equals(Guid.Empty)) { - playlist.OwnerUserId = guid; + if (item is Playlist playlist) + { + playlist.OwnerUserId = guid; + } } - } - break; - } + break; + } case "Format3D": - { - var val = reader.ReadNormalizedString(); - - if (item is Video video) { - if (string.Equals("HSBS", val, StringComparison.OrdinalIgnoreCase)) - { - video.Video3DFormat = Video3DFormat.HalfSideBySide; - } - else if (string.Equals("HTAB", val, StringComparison.OrdinalIgnoreCase)) - { - video.Video3DFormat = Video3DFormat.HalfTopAndBottom; - } - else if (string.Equals("FTAB", val, StringComparison.OrdinalIgnoreCase)) + var val = reader.ReadNormalizedString(); + + if (item is Video video) { - video.Video3DFormat = Video3DFormat.FullTopAndBottom; + if (string.Equals("HSBS", val, StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.HalfSideBySide; + } + else if (string.Equals("HTAB", val, StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.HalfTopAndBottom; + } + else if (string.Equals("FTAB", val, StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.FullTopAndBottom; + } + else if (string.Equals("FSBS", val, StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.FullSideBySide; + } + else if (string.Equals("MVC", val, StringComparison.OrdinalIgnoreCase)) + { + video.Video3DFormat = Video3DFormat.MVC; + } } - else if (string.Equals("FSBS", val, StringComparison.OrdinalIgnoreCase)) + + break; + } + + default: + { + string readerName = reader.Name; + if (_validProviderIds!.TryGetValue(readerName, out string? providerIdValue)) { - video.Video3DFormat = Video3DFormat.FullSideBySide; + var id = reader.ReadNormalizedString(); + item.TrySetProviderId(providerIdValue, id); } - else if (string.Equals("MVC", val, StringComparison.OrdinalIgnoreCase)) + else { - video.Video3DFormat = Video3DFormat.MVC; + reader.Skip(); } - } - break; - } - - default: - { - string readerName = reader.Name; - if (_validProviderIds!.TryGetValue(readerName, out string? providerIdValue)) - { - var id = reader.ReadNormalizedString(); - item.TrySetProviderId(providerIdValue, id); + break; } - else - { - reader.Skip(); - } - - break; - } } } @@ -526,31 +526,31 @@ namespace MediaBrowser.LocalMetadata.Parsers switch (reader.Name) { case "Share": - { - if (reader.IsEmptyElement) { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - var child = GetShare(subReader); + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } - if (child is not null) + using (var subReader = reader.ReadSubtree()) { - list.Add(child); + var child = GetShare(subReader); + + if (child is not null) + { + list.Add(child); + } } - } - break; - } + break; + } default: - { - reader.Skip(); - break; - } + { + reader.Skip(); + break; + } } } else @@ -780,7 +780,8 @@ namespace MediaBrowser.LocalMetadata.Parsers } /// <summary> - /// Get linked child. + /// Get linked child from XML. Uses deprecated Path/LibraryItemId properties for backward compatibility + /// with existing XML files. These will be resolved to ItemId when the linked child is accessed. /// </summary> /// <param name="reader">The xml reader.</param> /// <returns>The linked child.</returns> @@ -791,6 +792,7 @@ namespace MediaBrowser.LocalMetadata.Parsers reader.MoveToContent(); reader.Read(); +#pragma warning disable CS0618 // Type or member is obsolete - reading legacy XML format for backward compatibility // Loop through each element while (!reader.EOF && reader.ReadState == ReadState.Interactive) { @@ -820,6 +822,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { return linkedItem; } +#pragma warning restore CS0618 return null; } diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index 00634de5b5..324505d17c 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -64,32 +64,32 @@ namespace MediaBrowser.LocalMetadata.Parsers switch (reader.Name) { case "CollectionItem": - { - if (!reader.IsEmptyElement) { - using (var subReader = reader.ReadSubtree()) + if (!reader.IsEmptyElement) { - var child = GetLinkedChild(subReader); - - if (child is not null) + using (var subReader = reader.ReadSubtree()) { - list.Add(child); + var child = GetLinkedChild(subReader); + + if (child is not null) + { + list.Add(child); + } } } - } - else - { - reader.Read(); - } + else + { + reader.Read(); + } - break; - } + break; + } default: - { - reader.Skip(); - break; - } + { + reader.Skip(); + break; + } } } else diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index e0277870d1..0bda9e300a 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -76,25 +76,25 @@ namespace MediaBrowser.LocalMetadata.Parsers switch (reader.Name) { case "PlaylistItem": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) { - var child = GetLinkedChild(subReader); + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } - if (child is not null) + using (var subReader = reader.ReadSubtree()) { - list.Add(child); + var child = GetLinkedChild(subReader); + + if (child is not null) + { + list.Add(child); + } } - } - break; - } + break; + } default: reader.Skip(); diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 025a815247..b0f51aec71 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -6,7 +7,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; -using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -467,41 +467,62 @@ namespace MediaBrowser.LocalMetadata.Savers } /// <summary> - /// ADd linked children. + /// Add linked children. /// </summary> /// <param name="item">The item.</param> /// <param name="writer">The xml writer.</param> /// <param name="pluralNodeName">The plural node name.</param> /// <param name="singularNodeName">The singular node name.</param> /// <returns>The task object representing the asynchronous operation.</returns> - private static async Task AddLinkedChildren(Folder item, XmlWriter writer, string pluralNodeName, string singularNodeName) + private async Task AddLinkedChildren(Folder item, XmlWriter writer, string pluralNodeName, string singularNodeName) { - var items = item.LinkedChildren + var linkedChildren = item.LinkedChildren .Where(i => i.Type == LinkedChildType.Manual) .ToList(); - if (items.Count == 0) + if (linkedChildren.Count == 0) { return; } - await writer.WriteStartElementAsync(null, pluralNodeName, null).ConfigureAwait(false); + // Batch-resolve all ItemIds to paths in a single query to avoid an N+1 round-trip per linked child + var idsToResolve = new HashSet<Guid>(); + foreach (var link in linkedChildren) + { + if (link.ItemId.HasValue && !link.ItemId.Value.Equals(Guid.Empty)) + { + idsToResolve.Add(link.ItemId.Value); + } + } - foreach (var link in items) + Dictionary<Guid, string?>? pathById = null; + if (idsToResolve.Count > 0) { - if (!string.IsNullOrWhiteSpace(link.Path) || !string.IsNullOrWhiteSpace(link.LibraryItemId)) + var batched = LibraryManager.GetItemList(new InternalItemsQuery { - await writer.WriteStartElementAsync(null, singularNodeName, null).ConfigureAwait(false); - if (!string.IsNullOrWhiteSpace(link.Path)) - { - await writer.WriteElementStringAsync(null, "Path", null, link.Path).ConfigureAwait(false); - } + ItemIds = [.. idsToResolve] + }); + pathById = new Dictionary<Guid, string?>(batched.Count); + foreach (var batchedItem in batched) + { + pathById[batchedItem.Id] = batchedItem.Path; + } + } - if (!string.IsNullOrWhiteSpace(link.LibraryItemId)) - { - await writer.WriteElementStringAsync(null, "ItemId", null, link.LibraryItemId).ConfigureAwait(false); - } + await writer.WriteStartElementAsync(null, pluralNodeName, null).ConfigureAwait(false); + + foreach (var link in linkedChildren) + { + string? path = null; + if (pathById is not null && link.ItemId.HasValue && pathById.TryGetValue(link.ItemId.Value, out var resolvedPath)) + { + path = resolvedPath; + } + if (!string.IsNullOrWhiteSpace(path)) + { + await writer.WriteStartElementAsync(null, singularNodeName, null).ConfigureAwait(false); + await writer.WriteElementStringAsync(null, "Path", null, path).ConfigureAwait(false); await writer.WriteEndElementAsync().ConfigureAwait(false); } } |
