aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.LocalMetadata
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.LocalMetadata')
-rw-r--r--MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs369
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs36
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs28
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs55
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);
}
}