package org.rr.jeborker.db.item; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.UUID; import java.util.logging.Level; import org.apache.jempbox.xmp.Thumbnail; import org.rr.commons.collection.TransformValueList; import org.rr.commons.log.LoggerFactory; import org.rr.commons.mufs.IResourceHandler; import org.rr.commons.mufs.MimeUtils; import org.rr.commons.mufs.ResourceHandlerFactory; import org.rr.commons.utils.ListUtils; import org.rr.jeborker.app.BasePathList; import org.rr.jeborker.app.preferences.APreferenceStore; import org.rr.jeborker.app.preferences.PreferenceStoreFactory; import org.rr.jeborker.db.DefaultDBManager; import org.rr.jeborker.gui.MainController; import org.rr.jeborker.metadata.IMetadataReader; import org.rr.jeborker.metadata.MetadataHandlerFactory; import org.rr.jeborker.metadata.MetadataProperty; import org.rr.pm.image.IImageProvider; import org.rr.pm.image.ImageProviderFactory; import org.rr.pm.image.ImageUtils; public class EbookPropertyItemUtils { private static final String ALL_BOOK_PATH_COLLECTION = "allBookPathCollection"; private static final String thumbnailFolder = APreferenceStore.getConfigDirectory() + "thumbs/"; static { IResourceHandler thumbnailFolderResource = ResourceHandlerFactory.getResourceHandler(thumbnailFolder); if(!thumbnailFolderResource.exists()) { try { thumbnailFolderResource.mkdirs(); } catch (IOException e) { LoggerFactory.getLogger().log(Level.SEVERE, "Could not create thumbnail folder " + thumbnailFolder, e); } } } /** * Reloads the given item from the database. * @param item Item to be reloaded. * @return The reloaded item or <code>null</code> if the item no longer exists in the database. */ public static EbookPropertyItem reloadEbookPropertyItem(EbookPropertyItem item) { EbookPropertyItem refreshed = (EbookPropertyItem) DefaultDBManager.getInstance().reload(item); return refreshed; } /** * Get the {@link EbookPropertyItem}s for the given {@link IResourceHandler}. * @param resourceLoader The {@link IResourceHandler} instance where the {@link EbookPropertyItem} should be fetched from the database. * @return The desired {@link EbookPropertyItem}s. */ public static List<EbookPropertyItem> getEbookPropertyItemByResource(IResourceHandler resourceLoader) { if(resourceLoader == null) { return Collections.emptyList(); } // Look for selected resources List<EbookPropertyItem> selectedEbookPropertyItems = MainController.getController().getSelectedEbookPropertyItems(); for (EbookPropertyItem ebookPropertyItem : selectedEbookPropertyItems) { if(ebookPropertyItem.getResourceHandler().equals(resourceLoader)) { return Collections.singletonList(ebookPropertyItem); } } // Query resource from database DefaultDBManager defaultDBManager = DefaultDBManager.getInstance(); return defaultDBManager.getObject(EbookPropertyItem.class, "file", resourceLoader.toString()); } /** * Creates a new {@link EbookPropertyItem} from the given resource but without any setup excepting the resource file name. * * @param resource The resource where the {@link EbookPropertyItem} should be created from. * @return The newly created {@link EbookPropertyItem}. */ public static EbookPropertyItem createBasicEbookPropertyItem(final IResourceHandler resource) { final EbookPropertyItem item = (EbookPropertyItem) DefaultDBManager.getInstance().newInstance(EbookPropertyItem.class); item.setFile(resource.getResourceString()); return item; } /** * Creates a new {@link EbookPropertyItem} from the given resource. * * @param resource The resource where the {@link EbookPropertyItem} should be created from. * @param topLevelBaseFolder The base folder where the {@link EbookPropertyItem} is located at. * @return The newly created {@link EbookPropertyItem}. */ public static EbookPropertyItem createEbookPropertyItem(final IResourceHandler resource, final IResourceHandler topLevelBaseFolder) { final EbookPropertyItem item = createBasicEbookPropertyItem(resource); item.setCreatedAt(new Date()); if(topLevelBaseFolder != null) { item.setBasePath(topLevelBaseFolder.getResourceString()); } item.setMimeType(resource.getMimeType(false)); item.setTimestamp(resource.getModifiedAt().getTime()); refreshEbookPropertyItem(item, resource, true); return item; } /** * Refreshed the data of the given {@link EbookPropertyItem} by rereading it's metadata. * @param item The item to be refreshed. * @param resource The resource file for the {@link EbookPropertyItem}. it's optional and can be null. */ public static void refreshEbookPropertyItem(final EbookPropertyItem item, IResourceHandler resource, boolean refreshCover) { if(resource == null) { resource = ResourceHandlerFactory.getResourceHandler(item.getFile()); } IMetadataReader reader = MetadataHandlerFactory.getReader(resource); if(reader != null) { final List<MetadataProperty> metadataProperties = reader.readMetadata(); reader.fillEbookPropertyItem(metadataProperties, item); if(refreshCover) { boolean refreshed = false; List<MetadataProperty> metadataByType = reader.getMetadataByType(false, metadataProperties, IMetadataReader.COMMON_METADATA_TYPES.COVER); if(metadataByType != null && !metadataByType.isEmpty()) { MetadataProperty metadataProperty = metadataByType.get(0); if(metadataProperty.getValues() != null && !metadataProperty.getValues().isEmpty()) { Object value = metadataProperty.getValues().get(0); if(value instanceof byte[]) { setupCoverData(item, (byte[]) value); refreshed = true; } } } if(!refreshed) { setupCoverData(item, null); } } item.setTimestamp(resource.getModifiedAt().getTime()); } } /** * Does the setup for the cover image bytes. A new {@link EbookPropertyCoverItem} is created * and stored and the reference is set to the given {@link EbookPropertyItem}. * * @param item The {@link EbookPropertyItem} instance to be setup. * @param imageData The data to be stored in a {@link EbookPropertyCoverItem} * @return The newly created {@link EbookPropertyCoverItem} with the cover data. */ public static void setupCoverData(final EbookPropertyItem item, byte[] imageData) { //create thumbnail to be stored try { if(imageData != null && imageData.length > 0) { IImageProvider imageProvider = ImageProviderFactory.getImageProvider(ResourceHandlerFactory.getVirtualResourceHandler(UUID.randomUUID().toString(), imageData)); if(imageProvider != null) { BufferedImage thumbnailImage = ImageUtils.scaleToHeight(imageProvider.getImage(), 100); //much faster cropping the thumbnail than the original sized cover image. BufferedImage cropedImage = ImageUtils.crop(thumbnailImage); byte[] thumbnailImageBytes = ImageUtils.getImageBytes(cropedImage, MimeUtils.MIME_JPEG); if(thumbnailImageBytes != null) { setCoverThumbnail(thumbnailImageBytes, item.getResourceHandler()); } else { deleteCoverThumbnail(item.getResourceHandler()); } } else { deleteCoverThumbnail(item.getResourceHandler()); } } else { deleteCoverThumbnail(item.getResourceHandler()); } } catch(Exception e) { LoggerFactory.getLogger().log(Level.WARNING, "Failed to store thumbnail for " + item.getResourceHandler().getName(), e); } } /** * Deletes the cover thumbnail for the given ebook {@link IResourceHandler}. * @param ebookResource The ebook where the cover thumbnail should be deleted for. * @throws IOException */ public static void deleteCoverThumbnail(final IResourceHandler ebookResource) throws IOException { IResourceHandler thumbnailResourceLoader = getCoverThumbnailResourceHandler(ebookResource); if(thumbnailResourceLoader.exists()) { thumbnailResourceLoader.delete(); } } /** * Stores the given thumbnail bytes for the given ebook {@link IResourceHandler}. * @param thumbnailData The thumbnail data to be stored. * @param ebookResource The ebook where the cover thumbnail should be stored for. * @throws IOException */ private static void setCoverThumbnail(byte[] thumbnailData, IResourceHandler ebookResource) throws IOException { if(thumbnailData != null && thumbnailData.length != 0) { IResourceHandler thumbnailResourceLoader = getCoverThumbnailResourceHandler(ebookResource); thumbnailResourceLoader.setContent(thumbnailData); } } /** * Get the cover thumbnail bytes for the given ebook resource. * @param ebookResource The ebook resource wehere the cover thumbnail bytes should be loaded for. * @return The desired cover thumbnail bytes or <code>null</code> if no cover is stored for the given ebook {@link IResourceHandler}. */ public static byte[] getCoverThumbnailBytes(final IResourceHandler ebookResource) { IResourceHandler coverThumbnail = getCoverThumbnail(ebookResource); if(coverThumbnail != null) { try { return coverThumbnail.getContent(); } catch (IOException e) { LoggerFactory.getLogger().log(Level.WARNING, "Failed to load cover thumbnail for " + ebookResource.getName(), e); } } return null; } /** * Get the cover thumbnail {@link IResourceHandler} for the given ebook {@link IResourceHandler}. * @param ebookResource The ebook {@link IResourceHandler} where the {@link Thumbnail} should be fetched for. * @return The desired {@link IResourceHandler} instance for the cover thumbnail. Returns <code>null</code> if * no cover is stored for the given ebook {@link IResourceHandler}. */ private static IResourceHandler getCoverThumbnail(final IResourceHandler ebookResource) { IResourceHandler coverThumbnailResourceHandler = getCoverThumbnailResourceHandler(ebookResource); if(coverThumbnailResourceHandler.exists()) { return coverThumbnailResourceHandler; } return null; } /** * Get the {@link IResourceHandler} pointing to the cover thumbnail for the given ebook {@link IResourceHandler}. * @param ebookResource The ebook {@link IResourceHandler} where the cover thumbnail {@link IResourceHandler} should be fetched for. * @return The desired cover thumbnail {@link IResourceHandler} instance. */ private static IResourceHandler getCoverThumbnailResourceHandler(IResourceHandler ebookResource) { String thumbnail = thumbnailFolder + UUID.nameUUIDFromBytes(ebookResource.toString().getBytes()) + ".jpg"; return ResourceHandlerFactory.getResourceHandler(thumbnail); } /** * Renames the cover thumbnail for the given source resource to one which matches to the given target {@link IResourceHandler}. * This always happens if the file name changes. * @param source The source to be renamed. * @param target The target {@link IResourceHandler} for the cover thumbnail. */ public static void renameCoverThumbnail(IResourceHandler source, IResourceHandler target) { IResourceHandler sourceThumbnailResourceHandler = getCoverThumbnailResourceHandler(source); if(sourceThumbnailResourceHandler.exists()) { IResourceHandler targetThumbnailResourceHandler = getCoverThumbnailResourceHandler(target); try { sourceThumbnailResourceHandler.moveTo(targetThumbnailResourceHandler, true); } catch (IOException e) { LoggerFactory.getLogger().log(Level.WARNING, String.format("Failed to rename thumbnail from %s to %s", sourceThumbnailResourceHandler, targetThumbnailResourceHandler), e); } } } /** * Stores the given path collection to the database. * @param path The path elements to be stored. */ public static void storePathElements(final Collection<String> path) { final List<String> oldPathElements = fetchPathElements(); final BasePathList basePathList = PreferenceStoreFactory.getPreferenceStore(PreferenceStoreFactory.DB_STORE).getBasePath(); String indexedString; if(oldPathElements != null && !oldPathElements.isEmpty()) { final HashSet<String> allElements = new HashSet<>(path.size()); for(String oldPathElement : oldPathElements) { if(basePathList.containsBasePathFor(oldPathElement)) { allElements.add(oldPathElement); } } allElements.addAll(path); indexedString = ListUtils.toIndexedString(allElements, File.pathSeparatorChar); } else { indexedString = ListUtils.toIndexedString(path, File.pathSeparatorChar); } PreferenceStoreFactory.getPreferenceStore(PreferenceStoreFactory.DB_STORE).addGenericEntryAsString(ALL_BOOK_PATH_COLLECTION, indexedString); } /** * Fetches these path elements previously stored with the {@link #storePathElements(Collection)} * method. */ public static List<String> fetchPathElements() { String indexedListString = PreferenceStoreFactory.getPreferenceStore(PreferenceStoreFactory.DB_STORE).getGenericEntryAsString(ALL_BOOK_PATH_COLLECTION, null); if(indexedListString == null) { return Collections.emptyList(); } return ListUtils.fromIndexString(indexedListString, File.pathSeparatorChar); } /** * Creates a list of {@link IResourceHandler} for the given {@link EbookPropertyItem} list. * @param ebookPropertyItems The {@link EbookPropertyItem} list which {@link IResourceHandler} should be returned. * @return The desired list, never returns <code>null</code>. */ public static List<IResourceHandler> createIResourceHandlerList(List<EbookPropertyItem> ebookPropertyItems) { return new TransformValueList<EbookPropertyItem, IResourceHandler>(ebookPropertyItems) { @Override public IResourceHandler transform(EbookPropertyItem source) { return source.getResourceHandler(); } }; } }