BaseXmlSaver.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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 BaseNfoSaver : IMetadataFileSaver
  26. {
  27. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  28. private static readonly Dictionary<string, string> CommonTags = new[] {
  29. "plot",
  30. "customrating",
  31. "lockdata",
  32. "type",
  33. "dateadded",
  34. "title",
  35. "rating",
  36. "year",
  37. "sorttitle",
  38. "mpaa",
  39. "mpaadescription",
  40. "aspectratio",
  41. "website",
  42. "collectionnumber",
  43. "tmdbid",
  44. "rottentomatoesid",
  45. "language",
  46. "tvcomid",
  47. "budget",
  48. "revenue",
  49. "tagline",
  50. "studio",
  51. "genre",
  52. "tag",
  53. "runtime",
  54. "actor",
  55. "criticratingsummary",
  56. "criticrating",
  57. "fileinfo",
  58. "director",
  59. "writer",
  60. "trailer",
  61. "premiered",
  62. "releasedate",
  63. "outline",
  64. "id",
  65. "votes",
  66. "credits",
  67. "originaltitle",
  68. "watched",
  69. "playcount",
  70. "lastplayed",
  71. "art",
  72. "resume",
  73. "biography",
  74. "formed",
  75. "review",
  76. "style",
  77. "imdbid",
  78. "imdb_id",
  79. "plotkeyword",
  80. "country",
  81. "audiodbalbumid",
  82. "audiodbartistid",
  83. "awardsummary",
  84. "enddate",
  85. "lockedfields",
  86. "metascore",
  87. "zap2itid",
  88. "tvrageid",
  89. "gamesdbid",
  90. "musicbrainzartistid",
  91. "musicbrainzalbumartistid",
  92. "musicbrainzalbumid",
  93. "musicbrainzreleasegroupid",
  94. "tvdbid",
  95. "collectionitem",
  96. "isuserfavorite",
  97. "userrating",
  98. "countrycode"
  99. }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  100. protected BaseNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
  101. {
  102. Logger = logger;
  103. XmlReaderSettingsFactory = xmlReaderSettingsFactory;
  104. UserDataManager = userDataManager;
  105. UserManager = userManager;
  106. LibraryManager = libraryManager;
  107. ConfigurationManager = configurationManager;
  108. FileSystem = fileSystem;
  109. }
  110. protected IFileSystem FileSystem { get; private set; }
  111. protected IServerConfigurationManager ConfigurationManager { get; private set; }
  112. protected ILibraryManager LibraryManager { get; private set; }
  113. protected IUserManager UserManager { get; private set; }
  114. protected IUserDataManager UserDataManager { get; private set; }
  115. protected ILogger Logger { get; private set; }
  116. protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; }
  117. protected ItemUpdateType MinimumUpdateType
  118. {
  119. get
  120. {
  121. return ItemUpdateType.MetadataDownload;
  122. }
  123. }
  124. public string Name
  125. {
  126. get
  127. {
  128. return SaverName;
  129. }
  130. }
  131. public static string SaverName
  132. {
  133. get
  134. {
  135. return "Emby Xml";
  136. }
  137. }
  138. public string GetSavePath(IHasMetadata item)
  139. {
  140. return GetLocalSavePath(item);
  141. }
  142. /// <summary>
  143. /// Gets the save path.
  144. /// </summary>
  145. /// <param name="item">The item.</param>
  146. /// <returns>System.String.</returns>
  147. protected abstract string GetLocalSavePath(IHasMetadata item);
  148. /// <summary>
  149. /// Gets the name of the root element.
  150. /// </summary>
  151. /// <param name="item">The item.</param>
  152. /// <returns>System.String.</returns>
  153. protected abstract string GetRootElementName(IHasMetadata item);
  154. /// <summary>
  155. /// Determines whether [is enabled for] [the specified item].
  156. /// </summary>
  157. /// <param name="item">The item.</param>
  158. /// <param name="updateType">Type of the update.</param>
  159. /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
  160. public abstract bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType);
  161. protected virtual List<string> GetTagsUsed()
  162. {
  163. return new List<string>();
  164. }
  165. public void Save(IHasMetadata item, CancellationToken cancellationToken)
  166. {
  167. var path = GetSavePath(item);
  168. using (var memoryStream = new MemoryStream())
  169. {
  170. Save(item, memoryStream, path);
  171. memoryStream.Position = 0;
  172. cancellationToken.ThrowIfCancellationRequested();
  173. SaveToFile(memoryStream, path);
  174. }
  175. }
  176. private void SaveToFile(Stream stream, string path)
  177. {
  178. FileSystem.CreateDirectory(Path.GetDirectoryName(path));
  179. var file = FileSystem.GetFileInfo(path);
  180. var wasHidden = false;
  181. // This will fail if the file is hidden
  182. if (file.Exists)
  183. {
  184. if (file.IsHidden)
  185. {
  186. FileSystem.SetHidden(path, false);
  187. wasHidden = true;
  188. }
  189. }
  190. using (var filestream = FileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
  191. {
  192. stream.CopyTo(filestream);
  193. }
  194. if (wasHidden || ConfigurationManager.Configuration.SaveMetadataHidden)
  195. {
  196. FileSystem.SetHidden(path, true);
  197. }
  198. }
  199. private void Save(IHasMetadata item, Stream stream, string xmlPath)
  200. {
  201. var settings = new XmlWriterSettings
  202. {
  203. Indent = true,
  204. Encoding = Encoding.UTF8,
  205. CloseOutput = false
  206. };
  207. using (XmlWriter writer = XmlWriter.Create(stream, settings))
  208. {
  209. var root = GetRootElementName(item);
  210. writer.WriteStartDocument(true);
  211. writer.WriteStartElement(root);
  212. var baseItem = item as BaseItem;
  213. if (baseItem != null)
  214. {
  215. AddCommonNodes(baseItem, writer, LibraryManager, UserManager, UserDataManager, FileSystem, ConfigurationManager);
  216. }
  217. WriteCustomElements(item, writer);
  218. var tagsUsed = GetTagsUsed();
  219. try
  220. {
  221. AddCustomTags(xmlPath, tagsUsed, writer, Logger, FileSystem);
  222. }
  223. catch (FileNotFoundException)
  224. {
  225. }
  226. catch (IOException)
  227. {
  228. }
  229. catch (XmlException ex)
  230. {
  231. Logger.ErrorException("Error reading existng xml", ex);
  232. }
  233. writer.WriteEndElement();
  234. writer.WriteEndDocument();
  235. }
  236. }
  237. protected abstract void WriteCustomElements(IHasMetadata item, XmlWriter writer);
  238. public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
  239. /// <summary>
  240. /// Adds the common nodes.
  241. /// </summary>
  242. /// <returns>Task.</returns>
  243. public static void AddCommonNodes(BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config)
  244. {
  245. var writtenProviderIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  246. }
  247. private static bool IsPersonType(PersonInfo person, string type)
  248. {
  249. return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
  250. }
  251. private void AddCustomTags(string path, List<string> xmlTagsUsed, XmlWriter writer, ILogger logger, IFileSystem fileSystem)
  252. {
  253. var settings = XmlReaderSettingsFactory.Create(false);
  254. settings.CheckCharacters = false;
  255. settings.IgnoreProcessingInstructions = true;
  256. settings.IgnoreComments = true;
  257. using (var fileStream = fileSystem.OpenRead(path))
  258. {
  259. using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
  260. {
  261. // Use XmlReader for best performance
  262. using (var reader = XmlReader.Create(streamReader, settings))
  263. {
  264. try
  265. {
  266. reader.MoveToContent();
  267. }
  268. catch (Exception ex)
  269. {
  270. logger.ErrorException("Error reading existing xml tags from {0}.", ex, path);
  271. return;
  272. }
  273. // Loop through each element
  274. while (reader.Read())
  275. {
  276. if (reader.NodeType == XmlNodeType.Element)
  277. {
  278. var name = reader.Name;
  279. if (!CommonTags.ContainsKey(name) && !xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase))
  280. {
  281. writer.WriteNode(reader, false);
  282. }
  283. else
  284. {
  285. reader.Skip();
  286. }
  287. }
  288. }
  289. }
  290. }
  291. }
  292. }
  293. }
  294. }