package org.rr.jeborker.metadata; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.rr.commons.log.LoggerFactory; import org.rr.commons.mufs.IResourceHandler; import org.rr.commons.utils.StringUtil; import org.rr.jeborker.db.item.EbookPropertyItem; class MultiMetadataHandler extends AMetadataHandler implements IMetadataReader, IMetadataWriter { private List<IResourceHandler> ebookResourceHandler; MultiMetadataHandler(List<IResourceHandler> ebookResourceHandler) { this.ebookResourceHandler = ebookResourceHandler; } @Override public List<IResourceHandler> getEbookResource() { return this.ebookResourceHandler; } @Override public List<MetadataProperty> readMetadata() { //read and collect metadata from the ebook resources final HashMap<COMMON_METADATA_TYPES, List<MetadataProperty>> metadata = new HashMap<IMetadataReader.COMMON_METADATA_TYPES, List<MetadataProperty>>(); for (int i = 0; i < ebookResourceHandler.size(); i++) { final IResourceHandler resourceHandler = ebookResourceHandler.get(i); final IMetadataReader reader = MetadataHandlerFactory.getReader(resourceHandler); final List<MetadataProperty> readMetadata = reader.readMetadata(); //collect all metadata separated by it's type (author, title, etc.) for(COMMON_METADATA_TYPES type : COMMON_METADATA_TYPES.values()) { List<MetadataProperty> typeMetadata = metadata.containsKey(type) ? metadata.get(type) : new ArrayList<MetadataProperty>(); typeMetadata.addAll(reader.getMetadataByType(true, readMetadata, type)); metadata.put(type, typeMetadata); } } //create MultiMetadataProperty final List<MetadataProperty> result = createMetadataProperties(metadata); return result; } /** * Create {@link MetadataProperty} list for the properties given with the <i>metadata</i> parameter. * @return List with {@link MetadataProperty}. Never returns <code>null</code>. */ protected List<MetadataProperty> createMetadataProperties(final HashMap<COMMON_METADATA_TYPES, List<MetadataProperty>> metadata) { final List<MetadataProperty> result = new ArrayList<>(); for (Map.Entry<COMMON_METADATA_TYPES, List<MetadataProperty>> entry : metadata.entrySet()) { final COMMON_METADATA_TYPES metadataType = entry.getKey(); final List<MetadataProperty> metadataValues = entry.getValue(); final List<Object> values = new ArrayList<Object>() { { for(MetadataProperty property : metadataValues) { add(property.getValueAsString()); } } }; result.add(new MultiMetadataProperty(metadataType.getName(), values)); } return result; } @Override public List<MetadataProperty> getSupportedMetadata() { return Collections.emptyList(); } @Override public void fillEbookPropertyItem(List<MetadataProperty> metadataProperties, EbookPropertyItem item) { for(COMMON_METADATA_TYPES type : COMMON_METADATA_TYPES.values()) { for(MetadataProperty metadataProperty : metadataProperties) { if(metadataProperty.getName().equals(type.getName())) { type.fillItem(metadataProperty, item); } } } } @Override public String getPlainMetadata() { return null; } @Override public String getPlainMetadataMime() { return null; } @Override public List<MetadataProperty> getMetadataByType(boolean create, List<MetadataProperty> props, COMMON_METADATA_TYPES type) { List<MetadataProperty> result = new ArrayList<>(); for(MetadataProperty prop : props) { if(prop.getName().equals(type.getName())) { result.add(prop); } } if(create && result.isEmpty() && isSupportedMultiMetadata(type)) { MultiMetadataProperty multiMetadataProperty = new MultiMetadataProperty(type.getName(), new ArrayList<>(Collections.singleton(null))); result.add(multiMetadataProperty); } return result; } /** * Tells if the given {@link COMMON_METADATA_TYPES} is available as * multi metadata property. Not all metadata are commonly supported * by each metadata writer instance. * * @return <code>true</code> for support and <code>false</code> otherwise. */ private boolean isSupportedMultiMetadata(final COMMON_METADATA_TYPES type) { switch(type) { case AUTHOR: case TITLE: case RATING: case GENRE: case SERIES_NAME: case COVER: return true; default: return false; } } @Override public void writeMetadata(List<MetadataProperty> props) { for (int i = 0; i < ebookResourceHandler.size(); i++) { final IResourceHandler resourceHandler = ebookResourceHandler.get(i); final IMetadataWriter writer = MetadataHandlerFactory.getWriter(resourceHandler); final IMetadataReader reader = MetadataHandlerFactory.getReader(resourceHandler); final List<MetadataProperty> readMetadata = new ArrayList<>(reader.readMetadata()); boolean change = false; for(COMMON_METADATA_TYPES type : COMMON_METADATA_TYPES.values()) { for(MetadataProperty prop : props) { if(type.getName().equals(prop.getName()) && !prop.getValues().isEmpty()) { final Object value = prop.getValues().get(0); final boolean createNew = !StringUtil.toString(value).isEmpty(); final List<MetadataProperty> metadataByType = reader.getMetadataByType(createNew, readMetadata, type); if(!metadataByType.isEmpty()) { final MultiMetadataPropertyDelegate metadataProperty = new MultiMetadataPropertyDelegate(metadataByType); Object oldValue = metadataProperty.getValues().isEmpty() ? null : metadataProperty.getValues().get(0); if(value != null && !value.equals(oldValue)) { if(!StringUtil.toString(value).isEmpty() && !readMetadata.contains(metadataProperty.getFirstMetadataProperty())) { //add if not already exists. readMetadata.add(metadataProperty.getFirstMetadataProperty()); } else if(StringUtil.toString(value).isEmpty()) { //remove empty metadata entries readMetadata.remove(metadataProperty.getFirstMetadataProperty()); } metadataProperty.setValue(value, 0); change = true; } } } } } if(change) { try { writer.writeMetadata(readMetadata); } catch (Exception e) { LoggerFactory.getLogger().log(Level.WARNING, "Writing metadata to " + resourceHandler + " has failed.", e); } } } } @Override public void storePlainMetadata(byte[] plainMetadata) { } /** * Helps to handle multiple metadata with the same type so the new value * is set to all metadata entries which already have the same value. */ private static class MultiMetadataPropertyDelegate { private List<MetadataProperty> metaData; private List<Object> metaDataRefValue; /** * Take sure that a min of one entry is in the given metadata. */ MultiMetadataPropertyDelegate(List<MetadataProperty> metaData) { this.metaData = metaData; this.metaDataRefValue = metaData.get(0).getValues(); } public List<Object> getValues() { return metaDataRefValue; } /** * Set the value to each MetadataProperty that already have the same value * as the first one. Other values won't be touched. * @param value The value to set. * @param idx The index auf the value. */ void setValue(final Object value, final int idx) { Object refValue = getRefValue(idx); for(MetadataProperty d : metaData) { if(d.getValues().get(0).equals(refValue)) { d.setValue(value, idx); } } } /** * Get the first {@link MetadataProperty} instance. */ MetadataProperty getFirstMetadataProperty() { return metaData.get(0); } private Object getRefValue(int idx) { return metaDataRefValue.get(idx); } } }