package org.rr.jeborker.metadata;
import static org.rr.commons.utils.StringUtil.EMPTY;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import javax.xml.namespace.QName;
import nl.siegmann.epublib.domain.Author;
import nl.siegmann.epublib.domain.Book;
import nl.siegmann.epublib.domain.Identifier;
import nl.siegmann.epublib.domain.MediaType;
import nl.siegmann.epublib.domain.Meta;
import nl.siegmann.epublib.domain.Metadata;
import nl.siegmann.epublib.domain.Resource;
import nl.siegmann.epublib.domain.Resources;
import nl.siegmann.epublib.epub.EpubWriter;
import org.rr.commons.log.LoggerFactory;
import org.rr.commons.mufs.IResourceHandler;
import org.rr.commons.mufs.ResourceHandlerFactory;
import org.rr.commons.utils.Base64;
import org.rr.commons.utils.compression.truezip.TrueZipUtils;
import org.rr.jeborker.app.FileRefreshBackground;
import org.rr.jeborker.gui.MainController;
import org.rr.pm.image.IImageProvider;
import org.rr.pm.image.ImageProviderFactory;
import org.rr.pm.image.ImageUtils;
class EPubLibMetadataWriter extends AEpubMetadataHandler implements IMetadataWriter {
public EPubLibMetadataWriter(IResourceHandler ebookResourceHandler) {
super(ebookResourceHandler);
}
@Override
public void writeMetadata(List<MetadataProperty> props) {
final IResourceHandler ebookResourceHandler = getEbookResource().get(0);
try {
boolean lazy = ebookResourceHandler.size() > 10000000; //10MB
final Book epub = readBook(ebookResourceHandler.getContentInputStream(), ebookResourceHandler, lazy);
setMetadata(epub, props);
writeBook(epub, ebookResourceHandler);
LoggerFactory.logInfo(this, "Metadata successfully written to " + ebookResourceHandler, null);
} catch (Exception e) {
LoggerFactory.logWarning(this, "could not write metadata to file " + ebookResourceHandler, e);
}
}
private void setMetadata(final Book epub, final List<MetadataProperty> props) {
final Metadata metadata = epub.getMetadata();
metadata.clearAll();
Iterator<MetadataProperty> propsIterator = props.iterator();
while(propsIterator.hasNext()) {
final EpubLibMetadataProperty<?> meta = (EpubLibMetadataProperty<?>) propsIterator.next();
if(EPUB_METADATA_TYPES.AUTHOR.getName().equals(meta.getName())) {
Author author = new Author(meta.getValueAsString());
author.setRelator(((Author) meta.getType()).getRelator());
metadata.addAuthor(author);
} else if(EPUB_METADATA_TYPES.TITLE.getName().equals(meta.getName())) {
metadata.addTitle(meta.getValueAsString());
} else if(EPUB_METADATA_TYPES.DESCRIPTION.getName().equals(meta.getName())) {
metadata.addDescription(meta.getValueAsString());
} else if(EPUB_METADATA_TYPES.PUBLISHER.getName().equals(meta.getName())) {
metadata.addPublisher(meta.getValueAsString());
} else if(EPUB_METADATA_TYPES.RIGHTS.getName().equals(meta.getName())) {
metadata.addRight(meta.getValueAsString());
} else if(EPUB_METADATA_TYPES.SUBJECT.getName().equals(meta.getName())) {
metadata.addSubject(meta.getValueAsString());
} else if(EPUB_METADATA_TYPES.TYPE.getName().equals(meta.getName())) {
metadata.addType(meta.getValueAsString());
} else if(EPUB_METADATA_TYPES.CONTRIBUTOR.getName().equals(meta.getName()) || meta.getType() instanceof Author) {
Author author = new Author(meta.getValueAsString());
author.setRelator(((Author) meta.getType()).getRelator());
metadata.addContributor(author);
} else if(EPUB_METADATA_TYPES.DATE.getName().equals(meta.getName())) {
Date date = meta.getValueAsDate();
if(date != null) {
metadata.addDate(new nl.siegmann.epublib.domain.Date(date));
} else {
LoggerFactory.log(Level.WARNING, this, "Skipping invalid date '" + meta.getValueAsString() + "' at " + getEbookResource().get(0));
}
} else if(EPUB_METADATA_TYPES.PUBLICATION_DATE.getName().equals(meta.getName()) || EPUB_METADATA_TYPES.CREATION_DATE.getName().equals(meta.getName()) || EPUB_METADATA_TYPES.MODIFICATION_DATE.getName().equals(meta.getName())) {
Date date = meta.getValueAsDate();
if(date != null) {
metadata.addDate(new nl.siegmann.epublib.domain.Date(date, ((nl.siegmann.epublib.domain.Date)meta.getType()).getEvent()));
} else {
metadata.addDate(new nl.siegmann.epublib.domain.Date(meta.getValueAsString(), ((nl.siegmann.epublib.domain.Date)meta.getType()).getEvent()));
}
} else if(EPUB_METADATA_TYPES.IDENTIFIER.getName().equals(meta.getName()) || EPUB_METADATA_TYPES.UUID.getName().equals(meta.getName()) || EPUB_METADATA_TYPES.ISBN.getName().equals(meta.getName())) {
Identifier identifier = new Identifier(((Identifier) meta.getType()).getScheme(), meta.getValueAsString());
metadata.addIdentifier(identifier);
} else if(meta.getType() instanceof QName) {
Object type = meta.getType();
String prefix = ((QName)type).getPrefix();
String namespaceURI = ((QName)type).getNamespaceURI();
String localPart = ((QName)type).getLocalPart();
QName qName = new QName(namespaceURI, localPart, prefix);
metadata.addOtherProperty(qName, meta.getValueAsString());
} else if(meta.getType() instanceof Meta) {
Object type = meta.getType();
Meta m = new Meta( ((Meta)type).getName(), meta.getValueAsString() );
metadata.addOtherMeta(m);
} else if(EPUB_METADATA_TYPES.LANGUAGE.getName().equals(meta.getName())) {
metadata.setLanguage(meta.getValueAsString());
} else if(EPUB_METADATA_TYPES.FORMAT.getName().equals(meta.getName())) {
metadata.setFormat(meta.getValueAsString());
} else if(EPUB_METADATA_TYPES.COVER.getName().equals(meta.getName())) {
boolean isCover = false;
if(meta.getValues() != null && !meta.getValues().isEmpty()) {
Object coverObjectValue = meta.getValues().get(0);
byte[] cover = null;
if(coverObjectValue instanceof byte[]) {
cover = (byte[]) meta.getValues().get(0);
} else if(coverObjectValue instanceof String) {
cover = Base64.decode((String) coverObjectValue);
}
if(cover != null) {
this.setCover(epub, meta, cover);
isCover = true;
}
}
if(!isCover) {
epub.setCoverImage(null);
}
} else {
Meta m = new Meta( meta.getName(), meta.getValueAsString() );
metadata.addOtherMeta(m);
}
}
}
private void writeBook(final Book epub, final IResourceHandler ebookResourceHandler) throws IOException {
FileRefreshBackground.setDisabled(true);
try {
final EpubWriter writer = new EpubWriter();
final IResourceHandler temporaryResourceLoader = ResourceHandlerFactory.getUniqueResourceHandler(ebookResourceHandler, "tmp");
writer.write(epub, temporaryResourceLoader.getContentOutputStream(false));
if(temporaryResourceLoader.size() > 0) {
temporaryResourceLoader.moveTo(ebookResourceHandler, true);
} else {
temporaryResourceLoader.delete();
}
} finally {
FileRefreshBackground.setDisabled(false);
}
}
private void setCover(final Book epub, final EpubLibMetadataProperty<?> meta, final byte[] cover) {
try {
if(meta.getHint(MetadataProperty.HINTS.COVER_FROM_EBOOK_FILE_NAME) != null) {
this.changeExistingCover(epub, meta);
} else {
final Resource oldCoverImage = epub.getCoverImage();
if(oldCoverImage != null) {
if(differes(oldCoverImage.getData(), cover)) {
String message = Bundle.getString("EPubLibMetadataWriter.overwriteCover.message");
String title = Bundle.getString("EPubLibMetadataWriter.overwriteCover.title");
int result = MainController.getController().showMessageBox(message, title, JOptionPane.YES_NO_CANCEL_OPTION, "EPubLibMetadataWriter_Cover_Ovwerwrite_Key", -1, true);
if(result == JOptionPane.YES_OPTION) {
this.replaceOldCover(epub, cover, oldCoverImage);
} else if(result == JOptionPane.NO_OPTION) {
this.createNewCover(epub, cover);
} else {
return;
}
} else {
epub.setCoverImage(oldCoverImage);
}
} else {
this.createNewCover(epub, cover);
}
}
} catch (Exception e) {
final IResourceHandler ebookResourceHandler = getEbookResource().get(0);
LoggerFactory.logWarning(getClass(), "could not write cover for " + ebookResourceHandler.getName(), e);
}
}
private boolean differes(byte[] source, byte[] target) {
if(source.length != target.length) {
return true;
}
for(int i = 0; i < source.length; i++) {
if(source[i] != target[i]) {
return true;
}
}
return false;
}
private void createNewCover(final Book epub, final byte[] cover) throws IOException {
final IResourceHandler imageResourceLoader = ResourceHandlerFactory.getVirtualResourceHandler("DummyImageCoverName", cover);
final String mimeType = imageResourceLoader.getMimeType(true);
final String fileExtension = mimeType.substring(mimeType.indexOf('/') + 1);
final Resource newCoverImage = new Resource(cover, new MediaType(mimeType, "." + mimeType.substring(mimeType.indexOf('/') + 1) ));
String coverFilePath = getCoverFile(epub, "cover", fileExtension);
newCoverImage.setHref(coverFilePath);
newCoverImage.setId("cover");
epub.setCoverImage(newCoverImage);
}
private void replaceOldCover(final Book epub, final byte[] cover, final Resource oldCoverImage) throws IOException {
final String targetConversionMime = oldCoverImage.getMediaType().getName();
final String oldCoverFileName = new File(oldCoverImage.getHref()).getName();
final IImageProvider coverImageProvider = ImageProviderFactory.getImageProvider(ResourceHandlerFactory.getVirtualResourceHandler(oldCoverFileName, cover));
final byte[] imageBytes = ImageUtils.getImageBytes(coverImageProvider.getImage(), targetConversionMime);
oldCoverImage.setData(imageBytes);
epub.setCoverImage(oldCoverImage);
}
private void changeExistingCover(final Book epub, final EpubLibMetadataProperty<?> meta) {
//use existing cover
String coverName = (String) meta.getHint(MetadataProperty.HINTS.COVER_FROM_EBOOK_FILE_NAME);
if(coverName.contains("//")) {
coverName = coverName.substring(coverName.indexOf("//") + 2);
}
Resources resources = epub.getResources();
Resources unlistedResources = epub.getUnlistedResources();
Resource coverImage = resources.getByHref(coverName);
Resource unlistedCoverImage = unlistedResources.getByHref(coverName);
if(coverImage != null) {
epub.setCoverImage(coverImage);
} else if(unlistedCoverImage != null) {
String href = unlistedCoverImage.getHref();
unlistedResources.remove(href);
if(href.indexOf('/') != -1) {
href = href.substring(href.indexOf('/') + 1);
}
unlistedCoverImage.setHref(href);
epub.setCoverImage(unlistedCoverImage);
} else {
while(coverName.indexOf('/') != -1) {
coverName = coverName.substring(coverName.indexOf('/') + 1);
coverImage = resources.getByHref(coverName) != null ? resources.getByHref(coverName) : unlistedResources.getByHref(coverName);
if(coverImage != null) {
epub.setCoverImage(coverImage);
break;
}
}
}
}
/**
* Gets a cover file name with path. The path is this one where other image files also be located at if there exists any.
* The cover file name is tested for uniqueness in the folder.
*/
private static String getCoverFile(final Book epub, String desiredFileName, String fileExtension) throws IOException {
final Collection<Resource> resources = epub.getResources().getAll();
String path = EMPTY;
for(Resource resource : resources) {
if(resource.getMediaType() != null && resource.getMediaType().getName() != null && resource.getMediaType().getName().startsWith("image")) {
String hrefParent = new File(resource.getHref()).getParent();
if(hrefParent != null) {
path = hrefParent + "/";
}
break;
}
}
final Object additional = new Object() {
int count = 0;
public String toString() {
if(count == 0) {
count ++;
return EMPTY;
}
return String.valueOf(count++);
}
};
String newCoverFileName;
while(epub.getResources().containsByHref(newCoverFileName = path + desiredFileName + additional.toString() + "." + fileExtension )) {
}
return newCoverFileName;
}
/**
* Write the given content to the file in the underlying zip.
* @param content The content to be added to the zip archive.
* @param file The file name for the content in the zip archive.
* @throws IOException
*/
private void writeZipData(byte[] content, final String file) throws IOException {
final IResourceHandler ebookResourceHandler = getEbookResource().get(0);
TrueZipUtils.add(ebookResourceHandler, file, new ByteArrayInputStream(content));
}
@Override
public void storePlainMetadata(byte[] plainMetadata) {
try {
final IResourceHandler ebookResourceHandler = getEbookResource().get(0);
this.writeZipData(plainMetadata, getOpfFile(ebookResourceHandler));
} catch(Exception e) {
LoggerFactory.logWarning(this, "Could not write metadata to " + getEbookResource(), e);
}
}
}