using System; using System.IO.Compression; using System.Threading; using System.Threading.Tasks; 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 inside the comic itself. /// public class InternalComicInfoProvider : 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 InternalComicInfoProvider(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. No internal XML in comic archive.", 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 = GetComicBookFile(item.Path); if (file is null) { return false; } return file.Exists && _fileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved; } private async Task LoadXml(ItemInfo info, CancellationToken cancellationToken) { var path = GetComicBookFile(info.Path)?.FullName; if (path is null) { return null; } try { // open the comic archive and try to get the ComicInfo.xml entry using var comicBookFile = await ZipFile.OpenReadAsync(path, cancellationToken).ConfigureAwait(false); var container = comicBookFile.GetEntry(ComicInfoReader.ComicRackMetaFile); if (container is null) { return null; } using var containerStream = await container.OpenAsync(cancellationToken).ConfigureAwait(false); var comicInfoXml = XDocument.LoadAsync(containerStream, LoadOptions.None, cancellationToken); return await comicInfoXml.ConfigureAwait(false); } catch (Exception e) { _logger.LogError(e, "could not load internal XML from {Path}", path); return null; } } private FileSystemMetadata? GetComicBookFile(string path) { var fileInfo = _fileSystem.GetFileSystemInfo(path); if (fileInfo.IsDirectory) { return null; } // only parse files that are known to have internal metadata if (!string.Equals(fileInfo.Extension, ".cbz", StringComparison.OrdinalIgnoreCase)) { return null; } return fileInfo; } }