aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs')
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs752
1 files changed, 752 insertions, 0 deletions
diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
new file mode 100644
index 0000000000..b52bae2ce7
--- /dev/null
+++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
@@ -0,0 +1,752 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Security;
+using System.Text;
+using System.Threading;
+using System.Xml;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Xml;
+
+namespace MediaBrowser.LocalMetadata.Savers
+{
+ public abstract class BaseXmlSaver : IMetadataFileSaver
+ {
+ private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+ private static readonly Dictionary<string, string> CommonTags = new[] {
+
+ "Added",
+ "AspectRatio",
+ "AudioDbAlbumId",
+ "AudioDbArtistId",
+ "AwardSummary",
+ "BirthDate",
+ "Budget",
+
+ // Deprecated. No longer saving in this field.
+ "certification",
+
+ "Chapters",
+ "ContentRating",
+ "Countries",
+ "CustomRating",
+ "CriticRating",
+ "CriticRatingSummary",
+ "DeathDate",
+ "DisplayOrder",
+ "EndDate",
+ "Genres",
+ "Genre",
+ "GamesDbId",
+
+ // Deprecated. No longer saving in this field.
+ "IMDB_ID",
+
+ "IMDB",
+
+ // Deprecated. No longer saving in this field.
+ "IMDbId",
+
+ "Language",
+ "LocalTitle",
+ "OriginalTitle",
+ "LockData",
+ "LockedFields",
+ "Format3D",
+ "Metascore",
+
+ // Deprecated. No longer saving in this field.
+ "MPAARating",
+
+ "MPAADescription",
+
+ "MusicBrainzArtistId",
+ "MusicBrainzAlbumArtistId",
+ "MusicBrainzAlbumId",
+ "MusicBrainzReleaseGroupId",
+
+ // Deprecated. No longer saving in this field.
+ "MusicbrainzId",
+
+ "Overview",
+ "ShortOverview",
+ "Persons",
+ "PlotKeywords",
+ "PremiereDate",
+ "ProductionYear",
+ "Rating",
+ "Revenue",
+ "RottenTomatoesId",
+ "RunningTime",
+
+ // Deprecated. No longer saving in this field.
+ "Runtime",
+
+ "SortTitle",
+ "Studios",
+ "Tags",
+
+ // Deprecated. No longer saving in this field.
+ "TagLine",
+
+ "Taglines",
+ "TMDbCollectionId",
+ "TMDbId",
+
+ // Deprecated. No longer saving in this field.
+ "Trailer",
+
+ "Trailers",
+ "TVcomId",
+ "TvDbId",
+ "Type",
+ "TVRageId",
+ "VoteCount",
+ "Website",
+ "Zap2ItId",
+ "CollectionItems",
+ "PlaylistItems",
+ "Shares"
+
+ }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+
+ public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
+ {
+ FileSystem = fileSystem;
+ ConfigurationManager = configurationManager;
+ LibraryManager = libraryManager;
+ UserManager = userManager;
+ UserDataManager = userDataManager;
+ Logger = logger;
+ XmlReaderSettingsFactory = xmlReaderSettingsFactory;
+ }
+
+ protected IFileSystem FileSystem { get; private set; }
+ protected IServerConfigurationManager ConfigurationManager { get; private set; }
+ protected ILibraryManager LibraryManager { get; private set; }
+ protected IUserManager UserManager { get; private set; }
+ protected IUserDataManager UserDataManager { get; private set; }
+ protected ILogger Logger { get; private set; }
+ protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; }
+
+ protected ItemUpdateType MinimumUpdateType
+ {
+ get
+ {
+ return ItemUpdateType.MetadataDownload;
+ }
+ }
+
+ public string Name
+ {
+ get
+ {
+ return XmlProviderUtils.Name;
+ }
+ }
+
+ public string GetSavePath(IHasMetadata item)
+ {
+ return GetLocalSavePath(item);
+ }
+
+ /// <summary>
+ /// Gets the save path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>System.String.</returns>
+ protected abstract string GetLocalSavePath(IHasMetadata item);
+
+ /// <summary>
+ /// Gets the name of the root element.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>System.String.</returns>
+ protected virtual string GetRootElementName(IHasMetadata item)
+ {
+ return "Item";
+ }
+
+ /// <summary>
+ /// Determines whether [is enabled for] [the specified item].
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="updateType">Type of the update.</param>
+ /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
+ public abstract bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType);
+
+ protected virtual List<string> GetTagsUsed()
+ {
+ return new List<string>();
+ }
+
+ public void Save(IHasMetadata item, CancellationToken cancellationToken)
+ {
+ var path = GetSavePath(item);
+
+ using (var memoryStream = new MemoryStream())
+ {
+ Save(item, memoryStream, path);
+
+ memoryStream.Position = 0;
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ SaveToFile(memoryStream, path);
+ }
+ }
+
+ private void SaveToFile(Stream stream, string path)
+ {
+ FileSystem.CreateDirectory(Path.GetDirectoryName(path));
+
+ var file = FileSystem.GetFileInfo(path);
+
+ var wasHidden = false;
+
+ // This will fail if the file is hidden
+ if (file.Exists)
+ {
+ if (file.IsHidden)
+ {
+ FileSystem.SetHidden(path, false);
+ wasHidden = true;
+ }
+ if (file.IsReadOnly)
+ {
+ FileSystem.SetReadOnly(path, false);
+ }
+ }
+
+ using (var filestream = FileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ {
+ stream.CopyTo(filestream);
+ }
+
+ if (wasHidden || ConfigurationManager.Configuration.SaveMetadataHidden)
+ {
+ FileSystem.SetHidden(path, true);
+ }
+ }
+
+ private void Save(IHasMetadata item, Stream stream, string xmlPath)
+ {
+ var settings = new XmlWriterSettings
+ {
+ Indent = true,
+ Encoding = Encoding.UTF8,
+ CloseOutput = false
+ };
+
+ using (XmlWriter writer = XmlWriter.Create(stream, settings))
+ {
+ var root = GetRootElementName(item);
+
+ writer.WriteStartDocument(true);
+
+ writer.WriteStartElement(root);
+
+ var baseItem = item as BaseItem;
+
+ if (baseItem != null)
+ {
+ AddCommonNodes(baseItem, writer, LibraryManager, UserManager, UserDataManager, FileSystem, ConfigurationManager);
+ }
+
+ WriteCustomElements(item, writer);
+
+ var tagsUsed = GetTagsUsed();
+
+ try
+ {
+ AddCustomTags(xmlPath, tagsUsed, writer, Logger, FileSystem);
+ }
+ catch (FileNotFoundException)
+ {
+
+ }
+ catch (IOException)
+ {
+
+ }
+ catch (XmlException ex)
+ {
+ Logger.ErrorException("Error reading existng xml", ex);
+ }
+
+ writer.WriteEndElement();
+
+ writer.WriteEndDocument();
+ }
+ }
+
+ protected abstract void WriteCustomElements(IHasMetadata item, XmlWriter writer);
+
+ public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
+
+ /// <summary>
+ /// Adds the common nodes.
+ /// </summary>
+ /// <returns>Task.</returns>
+ public static void AddCommonNodes(BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config)
+ {
+ var writtenProviderIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+
+ if (!string.IsNullOrEmpty(item.OfficialRating))
+ {
+ writer.WriteElementString("ContentRating", item.OfficialRating);
+ }
+
+ if (!string.IsNullOrEmpty(item.OfficialRatingDescription))
+ {
+ writer.WriteElementString("MPAADescription", item.OfficialRatingDescription);
+ }
+
+ writer.WriteElementString("Added", item.DateCreated.ToLocalTime().ToString("G"));
+
+ writer.WriteElementString("LockData", item.IsLocked.ToString().ToLower());
+
+ if (item.LockedFields.Count > 0)
+ {
+ writer.WriteElementString("LockedFields", string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray()));
+ }
+
+ if (!string.IsNullOrEmpty(item.DisplayMediaType))
+ {
+ writer.WriteElementString("Type", item.DisplayMediaType);
+ }
+
+ if (item.CriticRating.HasValue)
+ {
+ writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(UsCulture));
+ }
+
+ if (!string.IsNullOrEmpty(item.CriticRatingSummary))
+ {
+ writer.WriteElementString("CriticRatingSummary", item.CriticRatingSummary);
+ }
+
+ if (!string.IsNullOrEmpty(item.Overview))
+ {
+ writer.WriteElementString("Overview", item.Overview);
+ }
+
+ if (!string.IsNullOrEmpty(item.OriginalTitle))
+ {
+ writer.WriteElementString("OriginalTitle", item.OriginalTitle);
+ }
+ if (!string.IsNullOrEmpty(item.ShortOverview))
+ {
+ writer.WriteElementString("ShortOverview", item.ShortOverview);
+ }
+ if (!string.IsNullOrEmpty(item.CustomRating))
+ {
+ writer.WriteElementString("CustomRating", item.CustomRating);
+ }
+
+ if (!string.IsNullOrEmpty(item.Name) && !(item is Episode))
+ {
+ writer.WriteElementString("LocalTitle", item.Name);
+ }
+
+ if (!string.IsNullOrEmpty(item.ForcedSortName))
+ {
+ writer.WriteElementString("SortTitle", item.ForcedSortName);
+ }
+
+ if (item.PremiereDate.HasValue)
+ {
+ if (item is Person)
+ {
+ writer.WriteElementString("BirthDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd"));
+ }
+ else if (!(item is Episode))
+ {
+ writer.WriteElementString("PremiereDate", item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd"));
+ }
+ }
+
+ if (item.EndDate.HasValue)
+ {
+ if (item is Person)
+ {
+ writer.WriteElementString("DeathDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd"));
+ }
+ else if (!(item is Episode))
+ {
+ writer.WriteElementString("EndDate", item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd"));
+ }
+ }
+
+ var hasTrailers = item as IHasTrailers;
+ if (hasTrailers != null)
+ {
+ if (hasTrailers.RemoteTrailers.Count > 0)
+ {
+ writer.WriteStartElement("Trailers");
+
+ foreach (var trailer in hasTrailers.RemoteTrailers)
+ {
+ writer.WriteElementString("Trailer", trailer.Url);
+ }
+
+ writer.WriteEndElement();
+ }
+ }
+
+ if (item.ProductionLocations.Count > 0)
+ {
+ writer.WriteStartElement("Countries");
+
+ foreach (var name in item.ProductionLocations)
+ {
+ writer.WriteElementString("Country", name);
+ }
+
+ writer.WriteEndElement();
+ }
+
+ var hasDisplayOrder = item as IHasDisplayOrder;
+ if (hasDisplayOrder != null && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder))
+ {
+ writer.WriteElementString("DisplayOrder", hasDisplayOrder.DisplayOrder);
+ }
+
+ var hasMetascore = item as IHasMetascore;
+ if (hasMetascore != null && hasMetascore.Metascore.HasValue)
+ {
+ writer.WriteElementString("Metascore", hasMetascore.Metascore.Value.ToString(UsCulture));
+ }
+
+ var hasAwards = item as IHasAwards;
+ if (hasAwards != null && !string.IsNullOrEmpty(hasAwards.AwardSummary))
+ {
+ writer.WriteElementString("AwardSummary", hasAwards.AwardSummary);
+ }
+
+ var hasBudget = item as IHasBudget;
+ if (hasBudget != null)
+ {
+ if (hasBudget.Budget.HasValue)
+ {
+ writer.WriteElementString("Budget", hasBudget.Budget.Value.ToString(UsCulture));
+ }
+
+ if (hasBudget.Revenue.HasValue)
+ {
+ writer.WriteElementString("Revenue", hasBudget.Revenue.Value.ToString(UsCulture));
+ }
+ }
+
+ if (item.CommunityRating.HasValue)
+ {
+ writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(UsCulture));
+ }
+ if (item.VoteCount.HasValue)
+ {
+ writer.WriteElementString("VoteCount", item.VoteCount.Value.ToString(UsCulture));
+ }
+
+ if (item.ProductionYear.HasValue && !(item is Person))
+ {
+ writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(UsCulture));
+ }
+
+ if (!string.IsNullOrEmpty(item.HomePageUrl))
+ {
+ writer.WriteElementString("Website", item.HomePageUrl);
+ }
+
+ var hasAspectRatio = item as IHasAspectRatio;
+ if (hasAspectRatio != null)
+ {
+ if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio))
+ {
+ writer.WriteElementString("AspectRatio", hasAspectRatio.AspectRatio);
+ }
+ }
+
+ if (!string.IsNullOrEmpty(item.PreferredMetadataLanguage))
+ {
+ writer.WriteElementString("Language", item.PreferredMetadataLanguage);
+ }
+ if (!string.IsNullOrEmpty(item.PreferredMetadataCountryCode))
+ {
+ writer.WriteElementString("CountryCode", item.PreferredMetadataCountryCode);
+ }
+
+ // Use original runtime here, actual file runtime later in MediaInfo
+ var runTimeTicks = item.RunTimeTicks;
+
+ if (runTimeTicks.HasValue)
+ {
+ var timespan = TimeSpan.FromTicks(runTimeTicks.Value);
+
+ writer.WriteElementString("RunningTime", Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture));
+ }
+
+ if (item.ProviderIds != null)
+ {
+ foreach (var providerKey in item.ProviderIds.Keys)
+ {
+ var providerId = item.ProviderIds[providerKey];
+ if (!string.IsNullOrEmpty(providerId))
+ {
+ writer.WriteElementString(providerKey + "Id", providerId);
+ }
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(item.Tagline))
+ {
+ writer.WriteStartElement("Taglines");
+ writer.WriteElementString("Tagline", item.Tagline);
+ writer.WriteEndElement();
+ }
+
+ if (item.Genres.Count > 0)
+ {
+ writer.WriteStartElement("Genres");
+
+ foreach (var genre in item.Genres)
+ {
+ writer.WriteElementString("Genre", genre);
+ }
+
+ writer.WriteEndElement();
+ }
+
+ if (item.Studios.Count > 0)
+ {
+ writer.WriteStartElement("Studios");
+
+ foreach (var studio in item.Studios)
+ {
+ writer.WriteElementString("Studio", studio);
+ }
+
+ writer.WriteEndElement();
+ }
+
+ if (item.Tags.Count > 0)
+ {
+ writer.WriteStartElement("Tags");
+
+ foreach (var tag in item.Tags)
+ {
+ writer.WriteElementString("Tag", tag);
+ }
+
+ writer.WriteEndElement();
+ }
+
+ if (item.Keywords.Count > 0)
+ {
+ writer.WriteStartElement("PlotKeywords");
+
+ foreach (var tag in item.Keywords)
+ {
+ writer.WriteElementString("PlotKeyword", tag);
+ }
+
+ writer.WriteEndElement();
+ }
+
+ var people = libraryManager.GetPeople(item);
+
+ if (people.Count > 0)
+ {
+ writer.WriteStartElement("Persons");
+
+ foreach (var person in people)
+ {
+ writer.WriteStartElement("Person");
+ writer.WriteElementString("Name", person.Name);
+ writer.WriteElementString("Type", person.Type);
+ writer.WriteElementString("Role", person.Role);
+
+ if (person.SortOrder.HasValue)
+ {
+ writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(UsCulture));
+ }
+
+ writer.WriteEndElement();
+ }
+
+ writer.WriteEndElement();
+ }
+
+ var boxset = item as BoxSet;
+ if (boxset != null)
+ {
+ AddLinkedChildren(boxset, writer, "CollectionItems", "CollectionItem");
+ }
+
+ var playlist = item as Playlist;
+ if (playlist != null)
+ {
+ AddLinkedChildren(playlist, writer, "PlaylistItems", "PlaylistItem");
+ }
+
+ var hasShares = item as IHasShares;
+ if (hasShares != null)
+ {
+ AddShares(hasShares, writer);
+ }
+
+ AddMediaInfo(item, writer);
+ }
+
+ public static void AddShares(IHasShares item, XmlWriter writer)
+ {
+ writer.WriteStartElement("Shares");
+
+ foreach (var share in item.Shares)
+ {
+ writer.WriteStartElement("Share");
+
+ writer.WriteElementString("UserId", share.UserId);
+ writer.WriteElementString("CanEdit", share.CanEdit.ToString().ToLower());
+
+ writer.WriteEndElement();
+ }
+
+ writer.WriteEndElement();
+ }
+
+ /// <summary>
+ /// Appends the media info.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public static void AddMediaInfo<T>(T item, XmlWriter writer)
+ where T : BaseItem
+ {
+ var video = item as Video;
+
+ if (video != null)
+ {
+ if (video.Video3DFormat.HasValue)
+ {
+ switch (video.Video3DFormat.Value)
+ {
+ case Video3DFormat.FullSideBySide:
+ writer.WriteElementString("Format3D", "FSBS");
+ break;
+ case Video3DFormat.FullTopAndBottom:
+ writer.WriteElementString("Format3D", "FTAB");
+ break;
+ case Video3DFormat.HalfSideBySide:
+ writer.WriteElementString("Format3D", "HSBS");
+ break;
+ case Video3DFormat.HalfTopAndBottom:
+ writer.WriteElementString("Format3D", "HTAB");
+ break;
+ case Video3DFormat.MVC:
+ writer.WriteElementString("Format3D", "MVC");
+ break;
+ }
+ }
+ }
+ }
+
+ public static void AddLinkedChildren(Folder item, XmlWriter writer, string pluralNodeName, string singularNodeName)
+ {
+ var items = item.LinkedChildren
+ .Where(i => i.Type == LinkedChildType.Manual)
+ .ToList();
+
+ if (items.Count == 0)
+ {
+ return;
+ }
+
+ writer.WriteStartElement(pluralNodeName);
+
+ foreach (var link in items)
+ {
+ if (!string.IsNullOrWhiteSpace(link.Path))
+ {
+ writer.WriteStartElement(singularNodeName);
+ writer.WriteElementString("Path", link.Path);
+ writer.WriteEndElement();
+ }
+ }
+
+ writer.WriteEndElement();
+ }
+
+ private static bool IsPersonType(PersonInfo person, string type)
+ {
+ return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private void AddCustomTags(string path, List<string> xmlTagsUsed, XmlWriter writer, ILogger logger, IFileSystem fileSystem)
+ {
+ var settings = XmlReaderSettingsFactory.Create(false);
+
+ settings.CheckCharacters = false;
+ settings.IgnoreProcessingInstructions = true;
+ settings.IgnoreComments = true;
+
+ using (var fileStream = fileSystem.OpenRead(path))
+ {
+ using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
+ {
+ try
+ {
+ reader.MoveToContent();
+ }
+ catch (Exception ex)
+ {
+ logger.ErrorException("Error reading existing xml tags from {0}.", ex, path);
+ return;
+ }
+
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ var name = reader.Name;
+
+ if (!CommonTags.ContainsKey(name) && !xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase))
+ {
+ writer.WriteNode(reader, false);
+ }
+ else
+ {
+ reader.Skip();
+ }
+ }
+ else
+ {
+ reader.Read();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}