All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dishevelled.thumbnail.AbstractThumbnailManager Maven / Gradle / Ivy

/*

    dsh-thumbnail  Implementation of the freedesktop.org thumbnail specification.
    Copyright (c) 2013-2019 held jointly by the individual authors.

    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 3 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; with out 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.

    > http://www.fsf.org/licensing/licenses/lgpl.html
    > http://www.opensource.org/licenses/lgpl-license.php

*/
package org.dishevelled.thumbnail;

import java.awt.image.BufferedImage;

import java.io.File;
import java.io.IOException;

import java.net.URI;
import java.net.URL;

import java.nio.file.Files;

import java.nio.file.attribute.PosixFilePermission;

import java.util.EnumSet;
import java.util.Set;

import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import org.apache.commons.codec.digest.DigestUtils;

import org.imgscalr.Scalr;

/**
 * Abstract thumbnail manager.
 *
 * @author  Michael Heuer
 */
public abstract class AbstractThumbnailManager implements ThumbnailManager
{
    /** Root thumbnail directory. */
    private final File directory;

    /** Directory for normal size (128x128 pixel) thumbnails. */
    private final File normalDirectory;

    /** Directory for large size (256x256 pixel) thumbnails. */
    private final File largeDirectory;

    /** Directory for metadata about failed thumbnail images. */
    private final File failDirectory;

    /** Maximum cache size. */
    private static final int MAXIMUM_SIZE = 1000;

    /** Cache expire after write, in seconds. */
    private static final int EXPIRE = 30;

    /** Cache of thumbnails keyed by thumbnail file. */
    private final LoadingCache cache = CacheBuilder.newBuilder()
        .maximumSize(MAXIMUM_SIZE)
        .expireAfterWrite(EXPIRE, TimeUnit.SECONDS)
        .build(new CacheLoader()
               {
                   @Override
                   public Thumbnail load(final File key) throws IOException
                   {
                       return Thumbnail.read(key);
                   }
               });

    /** Normal (128x128 pixel) size. */
    private static final int NORMAL_SIZE = 128;

    /** Large (256x256 pixel) size. */
    private static final int LARGE_SIZE = 256;

    /** Directory permissions. */
    private static final Set DIRECTORY_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ,
                                                                                     PosixFilePermission.OWNER_WRITE,
                                                                                     PosixFilePermission.OWNER_EXECUTE);
    /** File permissions. */
    private static final Set FILE_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ,
                                                                                PosixFilePermission.OWNER_WRITE);


    /**
     * Create a new abstract thumbnail manager with the specified root thumbnail directory.
     *
     * @param directory root thumbnail directory, must not be null
     */
    protected AbstractThumbnailManager(final File directory)
    {
        if (directory == null)
        {
            throw new IllegalArgumentException("directory must not be null");
        }
        this.directory = directory;
        normalDirectory = new File(this.directory, "normal");
        largeDirectory = new File(this.directory, "large");
        // todo: fail directory should be application specific; e.g. fail/dsh-thumbnail-1.0/
        failDirectory = new File(this.directory, "fail");
        normalDirectory.mkdirs();
        largeDirectory.mkdirs();
        failDirectory.mkdirs();

        fixDirectoryPermissions(this.directory);
        fixDirectoryPermissions(normalDirectory);
        fixDirectoryPermissions(largeDirectory);
        fixDirectoryPermissions(failDirectory);
    }


    /**
     * Fix the permissions of the specified file.
     *
     * @param file file
     */
    private void fixPermissions(final File file)
    {
        try
        {
            // set to 600
            Files.setPosixFilePermissions(file.toPath(), FILE_PERMISSIONS);
        }
        catch (Exception e)
        {
            // ignore
        }
    }

    /**
     * Fix the permissions of the specified directory.
     *
     * @param directory directory
     */
    private void fixDirectoryPermissions(final File directory)
    {
        try
        {
            // set to 700
            Files.setPosixFilePermissions(directory.toPath(), DIRECTORY_PERMISSIONS);
        }
        catch (Exception e)
        {
            // ignore
        }
    }

    /**
     * Attempt to create an empty fail file.
     *
     * @param failFile fail file to create
     */
    private void createFailFile(final File failFile)
    {
        try
        {
            failFile.createNewFile();
        }
        catch (IOException e)
        {
            // ignore
        }
    }

    /**
     * Create and return a thumbnail image for the specified URI.
     *
     * @param uri URI for the original image, must not be null
     * @param modificationTime modification time for the original image
     * @param thumbnailDirectory thumbnail directory
     * @param size size
     * @return a thumbnail image for the specified URI
     * @throws IOException if an I/O error occurs
     */
    private BufferedImage createThumbnail(final URI uri,
                                          final long modificationTime,
                                          final File thumbnailDirectory,
                                          final int size) throws IOException
    {
        if (uri == null)
        {
            throw new IllegalArgumentException("uri must not be null");
        }
        String fileName = DigestUtils.md5Hex(uri.toString()) + ".png";

        // fail fast if fail file exists
        File failFile = new File(failDirectory, fileName);
        if (failFile.exists())
        {
            throw new IOException("cannot create a thumbnail image for " + uri);
        }

        // check if thumbnail exists
        File thumbnailFile = new File(thumbnailDirectory, fileName);
        if (thumbnailFile.exists())
        {
            Thumbnail thumbnail = cache.getUnchecked(thumbnailFile);
            if (thumbnail.getModificationTime() == modificationTime)
            {
                return thumbnail.getImage();
            }
        }

        // load the image and create new thumbnail
        URL url = uri.toURL();
        BufferedImage image = null;
        try
        {
            image = ImageIO.read(url);
        }
        catch (IOException e)
        {
            createFailFile(failFile);
            throw new IOException("cannot create a thumbnail image for " + uri, e);
        }
        if (image == null)
        {
            createFailFile(failFile);
            throw new IOException("cannot create a thumbnail image for " + uri);
        }
        Thumbnail thumbnail = new Thumbnail(uri, modificationTime, image.getWidth(), image.getHeight(),
                                            Scalr.resize(image, size));

        File tmp = File.createTempFile("tmp", ".png", thumbnailDirectory);
        fixPermissions(tmp);
        thumbnail.write(tmp);
        com.google.common.io.Files.move(tmp, thumbnailFile);

        return thumbnail.getImage();
    }

    @Override
    public final BufferedImage createThumbnail(final URI uri, final long modificationTime) throws IOException
    {
        return createThumbnail(uri, modificationTime, normalDirectory, NORMAL_SIZE);
    }

    @Override
    public final BufferedImage createLargeThumbnail(final URI uri, final long modificationTime) throws IOException
    {
        return createThumbnail(uri, modificationTime, largeDirectory, LARGE_SIZE);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy