using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; 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.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 BaseNfoSaver : IMetadataFileSaver { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static readonly Dictionary CommonTags = new[] { "plot", "customrating", "lockdata", "type", "dateadded", "title", "rating", "year", "sorttitle", "mpaa", "mpaadescription", "aspectratio", "website", "collectionnumber", "tmdbid", "rottentomatoesid", "language", "tvcomid", "budget", "revenue", "tagline", "studio", "genre", "tag", "runtime", "actor", "criticratingsummary", "criticrating", "fileinfo", "director", "writer", "trailer", "premiered", "releasedate", "outline", "id", "votes", "credits", "originaltitle", "watched", "playcount", "lastplayed", "art", "resume", "biography", "formed", "review", "style", "imdbid", "imdb_id", "plotkeyword", "country", "audiodbalbumid", "audiodbartistid", "awardsummary", "enddate", "lockedfields", "metascore", "zap2itid", "tvrageid", "gamesdbid", "musicbrainzartistid", "musicbrainzalbumartistid", "musicbrainzalbumid", "musicbrainzreleasegroupid", "tvdbid", "collectionitem", "isuserfavorite", "userrating", "countrycode" }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); protected BaseNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) { Logger = logger; XmlReaderSettingsFactory = xmlReaderSettingsFactory; UserDataManager = userDataManager; UserManager = userManager; LibraryManager = libraryManager; ConfigurationManager = configurationManager; FileSystem = fileSystem; } 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 SaverName; } } public static string SaverName { get { return "Emby Xml"; } } public string GetSavePath(IHasMetadata item) { return GetLocalSavePath(item); } /// /// Gets the save path. /// /// The item. /// System.String. protected abstract string GetLocalSavePath(IHasMetadata item); /// /// Gets the name of the root element. /// /// The item. /// System.String. protected abstract string GetRootElementName(IHasMetadata item); /// /// Determines whether [is enabled for] [the specified item]. /// /// The item. /// Type of the update. /// true if [is enabled for] [the specified item]; otherwise, false. public abstract bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType); protected virtual List GetTagsUsed() { return new List(); } 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; } } 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"; /// /// Adds the common nodes. /// /// Task. public static void AddCommonNodes(BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config) { var writtenProviderIds = new HashSet(StringComparer.OrdinalIgnoreCase); } 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 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; } // Loop through each element while (reader.Read()) { 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(); } } } } } } } } }