using System; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Books.ComicInfo; /// /// Handles metadata for comics which is saved as an XML document. This XML document is not part /// of the comic itself but an external file. /// public class ExternalComicInfoProvider : IComicProvider { private readonly IFileSystem _fileSystem; private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. public ExternalComicInfoProvider(IFileSystem fileSystem, ILogger logger) { _logger = logger; _fileSystem = fileSystem; } /// public async ValueTask> ReadMetadata(ItemInfo info, IDirectoryService directoryService, CancellationToken cancellationToken) { var comicInfoXml = await LoadXml(info, cancellationToken).ConfigureAwait(false); if (comicInfoXml is null) { _logger.LogInformation("Could not load ComicInfo metadata for {Path} from XML file.", info.Path); return new MetadataResult { HasMetadata = false }; } var book = ComicInfoReader.ReadComicBookMetadata(comicInfoXml); if (book is null) { return new MetadataResult { HasMetadata = false }; } var metadataResult = new MetadataResult { Item = book, HasMetadata = true }; ComicInfoReader.ReadPeopleMetadata(comicInfoXml, metadataResult); ComicInfoReader.ReadCultureInfoInto(comicInfoXml, "ComicInfo/LanguageISO", cultureInfo => metadataResult.ResultLanguage = cultureInfo.ThreeLetterISOLanguageName); return metadataResult; } /// public bool HasItemChanged(BaseItem item) { var file = GetXmlFilePath(item.Path); return file.Exists && _fileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved; } private async Task LoadXml(ItemInfo info, CancellationToken cancellationToken) { var path = GetXmlFilePath(info.Path).FullName; if (path is null) { return null; } try { using var reader = XmlReader.Create(path, new XmlReaderSettings { Async = true }); var comicInfoXml = XDocument.LoadAsync(reader, LoadOptions.None, cancellationToken); return await comicInfoXml.ConfigureAwait(false); } catch (Exception e) { _logger.LogInformation(e, "Could not load external XML from {Path}. This could mean there is no separate ComicInfo metadata file for this comic or the metadata is bundled within the comic.", path); return null; } } private FileSystemMetadata GetXmlFilePath(string path) { var fileInfo = _fileSystem.GetFileSystemInfo(path); var directoryInfo = fileInfo.IsDirectory ? fileInfo : _fileSystem.GetDirectoryInfo(Path.GetDirectoryName(path)!); var file = _fileSystem.GetFileInfo(Path.Combine(directoryInfo.FullName, Path.GetFileNameWithoutExtension(path) + ".xml")); return file.Exists ? file : _fileSystem.GetFileInfo(Path.Combine(directoryInfo.FullName, ComicInfoReader.ComicRackMetaFile)); } }