/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.ui.assetpanel; import java.awt.Image; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.io.File; import java.io.FilenameFilter; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; import com.t3.client.TabletopTool; import com.t3.model.Token; import com.t3.persistence.PersistenceUtil; public class AssetDirectory extends Directory { public static final String PROPERTY_IMAGE_LOADED = "imageLoaded"; private final Map<File, FutureTask<Image>> imageMap = new HashMap<File, FutureTask<Image>>(); private static final Image INVALID_IMAGE = new BufferedImage(1, 1, Transparency.OPAQUE); private static ExecutorService largeImageLoaderService = Executors.newFixedThreadPool(1); private static ExecutorService smallImageLoaderService = Executors.newFixedThreadPool(2); private AtomicBoolean continueProcessing = new AtomicBoolean(true); public AssetDirectory(File directory, FilenameFilter fileFilter) { super(directory, fileFilter); } @Override public String toString() { return getPath().getName(); } @Override public void refresh() { imageMap.clear(); // Tell any in-progress processing to stop AtomicBoolean oldBool = continueProcessing; continueProcessing = new AtomicBoolean(true); oldBool.set(false); super.refresh(); } /** * Returns the asset associated with this file, or null if the file has not yet been loaded as an asset * * @param imageFile * @return */ public Image getImageFor(File imageFile) { FutureTask<Image> future = imageMap.get(imageFile); if (future != null) { if (future.isDone()) { try { return future.get() != INVALID_IMAGE ? future.get() : null; } catch (InterruptedException e) { // TODO: need to indicate a broken image return null; } catch (ExecutionException e) { // TODO: need to indicate a broken image return null; } } // Not done loading yet, don't block return null; } // load the asset in the background future = new FutureTask<Image>(new ImageLoader(imageFile)) { @Override protected void done() { firePropertyChangeEvent(new PropertyChangeEvent(AssetDirectory.this, PROPERTY_IMAGE_LOADED, false, true)); } }; if (imageFile.length() < 30 * 1024) { smallImageLoaderService.execute(future); } else { largeImageLoaderService.execute(future); } imageMap.put(imageFile, future); return null; } @Override protected Directory newDirectory(File directory, FilenameFilter fileFilter) { return new AssetDirectory(directory, fileFilter); } private class ImageLoader implements Callable<Image> { private final File imageFile; public ImageLoader(File imageFile) { this.imageFile = imageFile; } @Override public Image call() throws Exception { // Have we been orphaned ? if (!continueProcessing.get()) { return null; } // Load it up Image thumbnail = null; try { if (imageFile.getName().toLowerCase().endsWith(Token.FILE_EXTENSION)) { thumbnail = PersistenceUtil.getTokenThumbnail(imageFile); } else { thumbnail = TabletopTool.getThumbnailManager().getThumbnail(imageFile); } } catch (Throwable t) { t.printStackTrace(); thumbnail = INVALID_IMAGE; } return thumbnail; } } }