/* * regain/Thumbnailer - A file search engine providing plenty of formats (Plugin) * Copyright (C) 2011 Come_IN Computerclubs (University of Siegen) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contact: Come_IN-Team <come_in-team@listserv.uni-siegen.de> */ package de.uni_siegen.wineme.come_in.thumbnailer; import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.Queue; import org.apache.log4j.Logger; import de.uni_siegen.wineme.come_in.thumbnailer.thumbnailers.Thumbnailer; import de.uni_siegen.wineme.come_in.thumbnailer.util.ChainedHashMap; import de.uni_siegen.wineme.come_in.thumbnailer.util.IOUtil; import de.uni_siegen.wineme.come_in.thumbnailer.util.StringUtil; import de.uni_siegen.wineme.come_in.thumbnailer.util.mime.MimeTypeDetector; /** * This class manages all available Thumbnailers. * Its purpose is to delegate a File to the appropriate Thumbnailer in order to get a Thumbnail of it. * This is done in a fall-through manner: If several Thumbnailer can handle a specific filetype, * all are tried until a Thumbnail could be created. * * Fill this class with available Thumbnailers via the registerThumbnailer()-Method. * Then call generateThumbnail(). * * @author Benjamin */ public class ThumbnailerManager implements Thumbnailer, ThumbnailerConstants { /** * @var Starting estimate of the number of mime types that the thumbnailer can manager */ private static final int DEFAULT_NB_MIME_TYPES = 40; /** * @var Starting estimate of the number of thumbnailers per mime type */ private static final int DEFAULT_NB_THUMBNAILERS_PER_MIME = 5; /** * @var MIME Type for "all MIME" within thumbnailers Hash */ private static final String ALL_MIME_WILDCARD = "*/*"; /** * @var Width of thumbnail picture to create (in Pixel) */ private int thumbWidth; /** * @var Height of thumbnail picture to create (in Pixel) */ private int thumbHeight; /** * @var Options for image resizer (currently unused) */ private int thumbOptions = 0; /** Folder under which new thumbnails should be filed */ private File thumbnailFolder; /** The logger for this class */ private static Logger mLog = Logger.getLogger(ThumbnailerManager.class); /** * Thumbnailers per MIME-Type they accept (ALL_MIME_WILDCARD for all) */ private ChainedHashMap<String, Thumbnailer> thumbnailers; /** * All Thumbnailers. */ private Queue<Thumbnailer> allThumbnailers; /** * Magic Mime Detection ... a wrapper class to Aperature's Mime thingies. */ private MimeTypeDetector mimeTypeDetector; /** * Initialise Thumbnail Manager */ public ThumbnailerManager() { // Execute close() when JVM shuts down (if it wasn't executed before). final ThumbnailerManager self = this; Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { IOUtil.quietlyClose(self); } }); thumbnailers = new ChainedHashMap<String, Thumbnailer>(DEFAULT_NB_MIME_TYPES, DEFAULT_NB_THUMBNAILERS_PER_MIME); allThumbnailers = new LinkedList<Thumbnailer>(); mimeTypeDetector = new MimeTypeDetector(); thumbHeight = THUMBNAIL_DEFAULT_HEIGHT; thumbWidth = THUMBNAIL_DEFAULT_WIDTH; } /* currently not used private static String generate_hash(String str) { return StringUtil.transpose_string(StringUtil.md5(str)); } */ /** * Calculate a thumbnail filename (via hashing). * * @param input Input file * @param checkExist If true: guarantuee that such a filename doesn't exist yet * @return The chosen filename */ public File chooseThumbnailFilename(File input, boolean checkExist) { if (thumbnailFolder == null) throw new RuntimeException("chooseThumbnailFilename cannot be run before a first call to setThumbnailFolder()"); if (input == null) throw new NullPointerException("Input file may not be null"); String hash = ""; //"_" + generate_hash(input.getAbsolutePath()); String prefix = input.getName().replace('.', '_'); int tries = 0; String suffix = ""; File output; do { if (tries > 0) { int suffix_length = tries / 4 + 1; // Simple (i.e. guessed) heuristic to add randomness if many files have the same name suffix = "-" + StringUtil.randomString(suffix_length); } String name = prefix + hash + suffix + ".png"; output = new File(thumbnailFolder, name); tries++; } while (checkExist && output.exists()); return output; } /** * Set the folder where the thumbnails should be generated by default * (if no output file is given). * * @param thumbnailPath Path where the future thumbnails will be written to * @throws FileDoesNotExistException If the given path is not writeable */ public void setThumbnailFolder(String thumbnailPath) throws FileDoesNotExistException { setThumbnailFolder(new File(thumbnailPath)); } /** * Set the folder where the thumbnails should be generated by default * (if no output file is given). * * @param thumbnailPath Path where the future thumbnails will be written to * @throws FileDoesNotExistException If the given path is not writeable */ public void setThumbnailFolder(File thumbnailPath) throws FileDoesNotExistException { FileDoesNotExistException.checkWrite(thumbnailPath, "The thumbnail folder", true, true); thumbnailFolder = thumbnailPath; } /** * Generate a Thumbnail. * The output file name is generated using a hashing scheme. * It is garantueed that an existing Thumbnail is not overwritten by this. * * @param input Input file that should be processed. * @return Name of Thumbnail-File generated. * @throws FileDoesNotExistException * @throws IOException * @throws ThumbnailerException */ public File createThumbnail(File input) throws FileDoesNotExistException, IOException, ThumbnailerException { File output = chooseThumbnailFilename(input, true); generateThumbnail(input, output); return output; } /** * Add a Thumbnailer-Class to the list of available Thumbnailers * Note that the order you add Thumbnailers may make a difference: * First added Thumbnailers are tried first, if one fails, the next * (that claims to be able to treat such a document) is tried. * (Thumbnailers that claim to treat all MIME Types are tried last, though.) * * @param thumbnailer Thumbnailer to add. */ public void registerThumbnailer(Thumbnailer thumbnailer) { String[] acceptMIME = thumbnailer.getAcceptedMIMETypes(); if (acceptMIME == null) thumbnailers.put(ALL_MIME_WILDCARD, thumbnailer); else { for (String mime: acceptMIME) thumbnailers.put(mime, thumbnailer); } allThumbnailers.add(thumbnailer); thumbnailer.setImageSize(thumbWidth, thumbHeight, thumbOptions); } /** * Instead of a deconstructor: * De-initialize ThumbnailManager and its thumbnailers. * * This functions should be called before termination of the program, * and Thumbnails can't be generated after calling this function. */ public void close() { if (allThumbnailers == null) return; // Already closed for (Thumbnailer thumbnailer: allThumbnailers) { try { thumbnailer.close(); } catch (IOException e) { mLog.error("Error during close of Thumbnailer:", e); } } thumbnailers = null; allThumbnailers = null; } /** * Generate a Thumbnail of the input file. * Try all available Thumbnailers and use the first that returns an image. * * MIME-Detection narrows the selection of Thumbnailers to try: * <li>First all Thumbnailers that declare to accept such a MIME Type are used * <li>Then all Thumbnailers that declare to accept all possible MIME Types. * * @param input Input file that should be processed * @param output File in which should be written * @param mimeType MIME-Type of input file (null if unknown) * @throws IOException If file cannot be read/written. * @throws ThumbnailerException If the thumbnailing process failed * (i.e., no thumbnailer could generate an Thumbnail. * The last ThumbnailerException is re-thrown.) */ public void generateThumbnail(File input, File output, String mimeType) throws IOException, ThumbnailerException { FileDoesNotExistException.check(input, "The input file"); FileDoesNotExistException.checkWrite(output, "The output file", true, false); boolean generated = false; // MIME might be known already (in case of recursive thumbnail managers) if (mimeType == null) { mimeType = mimeTypeDetector.getMimeType(input); mLog.debug("Detected Mime-Typ: " + mimeType); } if (mimeType != null) generated = executeThumbnailers(mimeType, input, output, mimeType); // Try again using wildcard thumbnailers if (!generated) generated = executeThumbnailers(ALL_MIME_WILDCARD, input, output, mimeType); if (!generated) throw new ThumbnailerException("No suitable Thumbnailer has been found. (File: " + input.getName() + " ; Detected MIME: " + mimeType + ")"); } /** * Generate a Thumbnail of the input file. * Try all available Thumbnailers and use the first that returns an image. * * MIME-Detection narrows the selection of Thumbnailers to try: * <li>First all Thumbnailers that declare to accept such a MIME Type are used * <li>Then all Thumbnailers that declare to accept all possible MIME Types. * * @param input Input file that should be processed * @param output File in which should be written * @throws IOException If file cannot be read/written. * @throws ThumbnailerException If the thumbnailing process failed * (i.e., no thumbnailer could generate an Thumbnail. * The last ThumbnailerException is re-thrown.) */ public void generateThumbnail(File input, File output) throws IOException, ThumbnailerException { generateThumbnail(input, output, null); } /** * Helper function for Thumbnail generation: * execute all thumbnailers of a given MimeType. * * * @param useMimeType Which MIME Type the thumbnailers should be taken from * @param input Input File that should be processed * @param output Output file where the image shall be written. * @param detectedMimeType MIME Type that was returned by automatic MIME Detection * @return True on success (1 thumbnailer could generate the output file). * @throws IOException Input file cannot be read, or output file cannot be written, or necessary temporary files could not be created. */ private boolean executeThumbnailers(String useMimeType, File input, File output, String detectedMimeType) throws IOException { for (Thumbnailer thumbnailer: thumbnailers.getIterable(useMimeType)) { try { thumbnailer.generateThumbnail(input, output, detectedMimeType); return true; } catch (ThumbnailerException e) { // This Thumbnailer apparently wasn't suitable, so try next mLog.warn("Warning: " + thumbnailer.getClass().getName() + " could not handle the file " + input.getName() + " (trying next)", e); } } return false; } /** * Set the image size of all following thumbnails. * * ThumbnailManager delegates this to all his containing Thumbailers. */ public void setImageSize(int width, int height, int imageResizeOptions) { thumbHeight = height; thumbWidth = width; thumbOptions = imageResizeOptions; if (thumbWidth < 0) thumbWidth = 0; if (thumbHeight < 0) thumbHeight = 0; for (Thumbnailer thumbnailer: allThumbnailers) thumbnailer.setImageSize(thumbWidth, thumbHeight, thumbOptions); } /** * Get the currently set Image Width of this Thumbnailer. * @return image width of created thumbnails. */ public int getCurrentImageWidth() { return thumbWidth; } /** * Get the currently set Image Height of this Thumbnailer. * @return image height of created thumbnails. */ public int getCurrentImageHeight() { return thumbHeight; } /** * Summarize all contained MIME Type Thumbnailers. * @return All accepted MIME Types, null if any. */ public String[] getAcceptedMIMETypes() { if (thumbnailers.containsKey(ALL_MIME_WILDCARD)) return null; // All MIME Types else return thumbnailers.keySet().toArray(new String[]{}); } }