BaseXmlSaver.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading;
  8. using System.Xml;
  9. using MediaBrowser.Common.Extensions;
  10. using MediaBrowser.Controller.Configuration;
  11. using MediaBrowser.Controller.Entities;
  12. using MediaBrowser.Controller.Entities.Audio;
  13. using MediaBrowser.Controller.Entities.Movies;
  14. using MediaBrowser.Controller.Entities.TV;
  15. using MediaBrowser.Controller.Library;
  16. using MediaBrowser.Controller.Persistence;
  17. using MediaBrowser.Model.Configuration;
  18. using MediaBrowser.Model.Entities;
  19. using MediaBrowser.Model.Extensions;
  20. using MediaBrowser.Model.IO;
  21. using MediaBrowser.Model.Logging;
  22. using MediaBrowser.Model.Xml;
  23. namespace MediaBrowser.LocalMetadata.Savers
  24. {
  25. public abstract class BaseXmlSaver : IMetadataFileSaver
  26. {
  27. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  28. private static readonly Dictionary<string, string> CommonTags = new[] {
  29. "Added",
  30. "AspectRatio",
  31. "AudioDbAlbumId",
  32. "AudioDbArtistId",
  33. "AwardSummary",
  34. "BirthDate",
  35. "Budget",
  36. // Deprecated. No longer saving in this field.
  37. "certification",
  38. "Chapters",
  39. "ContentRating",
  40. "Countries",
  41. "CustomRating",
  42. "CriticRating",
  43. "CriticRatingSummary",
  44. "DeathDate",
  45. "DisplayOrder",
  46. "EndDate",
  47. "Genres",
  48. "Genre",
  49. "GamesDbId",
  50. // Deprecated. No longer saving in this field.
  51. "IMDB_ID",
  52. "IMDB",
  53. // Deprecated. No longer saving in this field.
  54. "IMDbId",
  55. "Language",
  56. "LocalTitle",
  57. "OriginalTitle",
  58. "LockData",
  59. "LockedFields",
  60. "Format3D",
  61. "Metascore",
  62. // Deprecated. No longer saving in this field.
  63. "MPAARating",
  64. "MPAADescription",
  65. "MusicBrainzArtistId",
  66. "MusicBrainzAlbumArtistId",
  67. "MusicBrainzAlbumId",
  68. "MusicBrainzReleaseGroupId",
  69. // Deprecated. No longer saving in this field.
  70. "MusicbrainzId",
  71. "Overview",
  72. "ShortOverview",
  73. "Persons",
  74. "PlotKeywords",
  75. "PremiereDate",
  76. "ProductionYear",
  77. "Rating",
  78. "Revenue",
  79. "RottenTomatoesId",
  80. "RunningTime",
  81. // Deprecated. No longer saving in this field.
  82. "Runtime",
  83. "SortTitle",
  84. "Studios",
  85. "Tags",
  86. // Deprecated. No longer saving in this field.
  87. "TagLine",
  88. "Taglines",
  89. "TMDbCollectionId",
  90. "TMDbId",
  91. // Deprecated. No longer saving in this field.
  92. "Trailer",
  93. "Trailers",
  94. "TVcomId",
  95. "TvDbId",
  96. "Type",
  97. "TVRageId",
  98. "VoteCount",
  99. "Website",
  100. "Zap2ItId",
  101. "CollectionItems",
  102. "PlaylistItems",
  103. "Shares"
  104. }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  105. public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
  106. {
  107. FileSystem = fileSystem;
  108. ConfigurationManager = configurationManager;
  109. LibraryManager = libraryManager;
  110. UserManager = userManager;
  111. UserDataManager = userDataManager;
  112. Logger = logger;
  113. XmlReaderSettingsFactory = xmlReaderSettingsFactory;
  114. }
  115. protected IFileSystem FileSystem { get; private set; }
  116. protected IServerConfigurationManager ConfigurationManager { get; private set; }
  117. protected ILibraryManager LibraryManager { get; private set; }
  118. protected IUserManager UserManager { get; private set; }
  119. protected IUserDataManager UserDataManager { get; private set; }
  120. protected ILogger Logger { get; private set; }
  121. protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; }
  122. protected ItemUpdateType MinimumUpdateType
  123. {
  124. get
  125. {
  126. return ItemUpdateType.MetadataDownload;
  127. }
  128. }
  129. public string Name
  130. {
  131. get
  132. {
  133. return XmlProviderUtils.Name;
  134. }
  135. }
  136. public string GetSavePath(IHasMetadata item)
  137. {
  138. return GetLocalSavePath(item);
  139. }
  140. /// <summary>
  141. /// Gets the save path.
  142. /// </summary>
  143. /// <param name="item">The item.</param>
  144. /// <returns>System.String.</returns>
  145. protected abstract string GetLocalSavePath(IHasMetadata item);
  146. /// <summary>
  147. /// Gets the name of the root element.
  148. /// </summary>
  149. /// <param name="item">The item.</param>
  150. /// <returns>System.String.</returns>
  151. protected abstract string GetRootElementName(IHasMetadata item);
  152. /// <summary>
  153. /// Determines whether [is enabled for] [the specified item].
  154. /// </summary>
  155. /// <param name="item">The item.</param>
  156. /// <param name="updateType">Type of the update.</param>
  157. /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
  158. public abstract bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType);
  159. protected virtual List<string> GetTagsUsed()
  160. {
  161. return new List<string>();
  162. }
  163. public void Save(IHasMetadata item, CancellationToken cancellationToken)
  164. {
  165. var path = GetSavePath(item);
  166. using (var memoryStream = new MemoryStream())
  167. {
  168. Save(item, memoryStream, path);
  169. memoryStream.Position = 0;
  170. cancellationToken.ThrowIfCancellationRequested();
  171. SaveToFile(memoryStream, path);
  172. }
  173. }
  174. private void SaveToFile(Stream stream, string path)
  175. {
  176. FileSystem.CreateDirectory(Path.GetDirectoryName(path));
  177. var file = FileSystem.GetFileInfo(path);
  178. var wasHidden = false;
  179. // This will fail if the file is hidden
  180. if (file.Exists)
  181. {
  182. if (file.IsHidden)
  183. {
  184. FileSystem.SetHidden(path, false);
  185. wasHidden = true;
  186. }
  187. }
  188. using (var filestream = FileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
  189. {
  190. stream.CopyTo(filestream);
  191. }
  192. if (wasHidden || ConfigurationManager.Configuration.SaveMetadataHidden)
  193. {
  194. FileSystem.SetHidden(path, true);
  195. }
  196. }
  197. private void Save(IHasMetadata item, Stream stream, string xmlPath)
  198. {
  199. var settings = new XmlWriterSettings
  200. {
  201. Indent = true,
  202. Encoding = Encoding.UTF8,
  203. CloseOutput = false
  204. };
  205. using (XmlWriter writer = XmlWriter.Create(stream, settings))
  206. {
  207. var root = GetRootElementName(item);
  208. writer.WriteStartDocument(true);
  209. writer.WriteStartElement(root);
  210. var baseItem = item as BaseItem;
  211. if (baseItem != null)
  212. {
  213. AddCommonNodes(baseItem, writer, LibraryManager, UserManager, UserDataManager, FileSystem, ConfigurationManager);
  214. }
  215. WriteCustomElements(item, writer);
  216. var tagsUsed = GetTagsUsed();
  217. try
  218. {
  219. AddCustomTags(xmlPath, tagsUsed, writer, Logger, FileSystem);
  220. }
  221. catch (FileNotFoundException)
  222. {
  223. }
  224. catch (IOException)
  225. {
  226. }
  227. catch (XmlException ex)
  228. {
  229. Logger.ErrorException("Error reading existng xml", ex);
  230. }
  231. writer.WriteEndElement();
  232. writer.WriteEndDocument();
  233. }
  234. }
  235. protected abstract void WriteCustomElements(IHasMetadata item, XmlWriter writer);
  236. public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
  237. /// <summary>
  238. /// Adds the common nodes.
  239. /// </summary>
  240. /// <returns>Task.</returns>
  241. public static void AddCommonNodes(BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config)
  242. {
  243. var writtenProviderIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  244. }
  245. private static bool IsPersonType(PersonInfo person, string type)
  246. {
  247. return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
  248. }
  249. private void AddCustomTags(string path, List<string> xmlTagsUsed, XmlWriter writer, ILogger logger, IFileSystem fileSystem)
  250. {
  251. var settings = XmlReaderSettingsFactory.Create(false);
  252. settings.CheckCharacters = false;
  253. settings.IgnoreProcessingInstructions = true;
  254. settings.IgnoreComments = true;
  255. using (var fileStream = fileSystem.OpenRead(path))
  256. {
  257. using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
  258. {
  259. // Use XmlReader for best performance
  260. using (var reader = XmlReader.Create(streamReader, settings))
  261. {
  262. try
  263. {
  264. reader.MoveToContent();
  265. }
  266. catch (Exception ex)
  267. {
  268. logger.ErrorException("Error reading existing xml tags from {0}.", ex, path);
  269. return;
  270. }
  271. // Loop through each element
  272. while (reader.Read())
  273. {
  274. if (reader.NodeType == XmlNodeType.Element)
  275. {
  276. var name = reader.Name;
  277. if (!CommonTags.ContainsKey(name) && !xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase))
  278. {
  279. writer.WriteNode(reader, false);
  280. }
  281. else
  282. {
  283. reader.Skip();
  284. }
  285. }
  286. }
  287. }
  288. }
  289. }
  290. }
  291. }
  292. }