package org.xmind.ui.internal.wizards; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.URIUtil; import org.eclipse.core.runtime.jobs.IJobFunction; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import org.xmind.core.IWorkbook; import org.xmind.core.util.FileUtils; import org.xmind.gef.image.ResizeConstants; import org.xmind.ui.internal.MindMapMessages; import org.xmind.ui.internal.MindMapUIPlugin; import org.xmind.ui.mindmap.ITemplate; import org.xmind.ui.mindmap.IWorkbookRef; import org.xmind.ui.mindmap.MindMap; import org.xmind.ui.mindmap.MindMapImageExporter; import org.xmind.ui.mindmap.MindMapUI; import org.xmind.ui.util.ImageFormat; public class TemplateLabelProvider extends LabelProvider implements ISchedulingRule { private static final String CACHES_TEMPLATES_DIR = "caches/templates/"; //$NON-NLS-1$ private static ImageFormat defaultFormat = ImageFormat.PNG; private Map<ITemplate, TemplateThumbnailImageLoader> imageLoaders = new HashMap<ITemplate, TemplateThumbnailImageLoader>(); private Properties cachedImagePathMap = null; private static class TemplateThumbnailImageLoader { private final TemplateLabelProvider owner; private final ITemplate template; private final Display display; private Image image; private boolean disposed; public TemplateThumbnailImageLoader(TemplateLabelProvider owner, ITemplate template, Display display) { this.owner = owner; this.template = template; this.display = display; this.image = null; this.disposed = false; Job initJob = Job.create(NLS.bind( MindMapMessages.TemplateLabelProvider_loadThumbnail_jobName, template.getName()), new IJobFunction() { @Override public IStatus run(IProgressMonitor monitor) { return doLoad(monitor); } }); initJob.setSystem(true); initJob.setRule(owner); MindMapUIPlugin.getDefault().registerJob(initJob); initJob.schedule(); } private IStatus doLoad(IProgressMonitor monitor) { final URI sourceWorkbookURI = template.getSourceWorkbookURI(); final Properties imagePathMap = owner.getImagePathMap(); String cachedImagePath = imagePathMap .getProperty(sourceWorkbookURI.toString()); File cachedImageFile; if (cachedImagePath != null) { cachedImageFile = new File(getCacheDirForTemplates(), cachedImagePath); if (cachedImageFile.isFile() && cachedImageFile.canRead()) { return loadImage(cachedImageFile); } } cachedImagePath = UUID.randomUUID().toString() + defaultFormat.getExtensions().get(0); cachedImageFile = new File(getCacheDirForTemplates(), cachedImagePath); if (URIUtil.isFileURI(sourceWorkbookURI)) { // try loading thumbnail image from source workbook in local file system File thumbnailFile = extractThumbnailImageFromLocalFile( URIUtil.toFile(sourceWorkbookURI), cachedImageFile); if (thumbnailFile != null && !thumbnailFile.equals(cachedImageFile)) { // load image directly from the returned file, // and do not save this file to cache return loadImage(thumbnailFile); } } if (cachedImageFile.isFile() && cachedImageFile.canRead()) { imagePathMap.put(sourceWorkbookURI.toString(), cachedImagePath); owner.saveImagePathMap(); return loadImage(cachedImageFile); } if (isDisposed() || monitor.isCanceled()) return Status.CANCEL_STATUS; // generate thumbnail image now final String targetImagePath = cachedImagePath; final File targetImageFile = cachedImageFile; final IWorkbookRef sourceWorkbookRef = MindMapUIPlugin.getDefault() .getWorkbookRefFactory() .createWorkbookRef(sourceWorkbookURI, null); if (sourceWorkbookRef == null) return new Status(IStatus.ERROR, MindMapUIPlugin.PLUGIN_ID, "Failed to obtain source workbook ref from: " //$NON-NLS-1$ + sourceWorkbookURI.toString()); SubMonitor subMonitor = SubMonitor.convert(monitor, 100); try { sourceWorkbookRef.open(subMonitor.newChild(30)); } catch (InterruptedException e) { throw new OperationCanceledException(); } catch (InvocationTargetException e) { Throwable cause = e.getTargetException(); if (cause == null) cause = e; if (cause instanceof org.eclipse.core.runtime.CoreException) return ((org.eclipse.core.runtime.CoreException) cause) .getStatus(); return new Status(IStatus.ERROR, MindMapUIPlugin.PLUGIN_ID, e.getMessage(), e); } try { if (isDisposed() || monitor.isCanceled()) throw new OperationCanceledException(); IWorkbook workbook = sourceWorkbookRef.getWorkbook(); Assert.isTrue(workbook != null); try { InputStream previewImageData = sourceWorkbookRef .getPreviewImageData( workbook.getPrimarySheet().getId(), null); if (previewImageData != null) { FileUtils.ensureFileParent(targetImageFile); OutputStream output = new FileOutputStream( targetImageFile); try { FileUtils.transfer(previewImageData, output); } finally { output.close(); } return loadImage(targetImageFile); } } catch (IOException e) { /// ignore errors here } return doGenerate(subMonitor.newChild(65), sourceWorkbookRef.getWorkbook(), targetImageFile, targetImagePath, sourceWorkbookURI, imagePathMap); } finally { try { sourceWorkbookRef.close(subMonitor.newChild(5)); } catch (InvocationTargetException e) { // ignore errors here } catch (InterruptedException e) { // ignore cancellation here } } } private File extractThumbnailImageFromLocalFile(File sourceWorkbookFile, File cachedImageFile) { String thumbnailArchivePath = MindMapImageExporter .toThumbnailArchivePath(defaultFormat); if (sourceWorkbookFile.isDirectory()) { File sourceThumbnailFile = new File(sourceWorkbookFile, thumbnailArchivePath); if (sourceThumbnailFile.isFile() && sourceThumbnailFile.canRead()) // use the source thumbnail image directly return sourceThumbnailFile; // source workbook does not contain thumbnail image, // generate thumbnail image later return null; } if (!sourceWorkbookFile.isFile() || !sourceWorkbookFile.canRead()) { // source workbook is not accessible, // generate thumbnail image later return null; } try { ZipInputStream sourceInput = new ZipInputStream( new FileInputStream(sourceWorkbookFile)); try { // find thumbnail image zip entry ZipEntry entry; do { entry = sourceInput.getNextEntry(); } while (entry != null && !thumbnailArchivePath.equals(entry.getName())); if (entry == null) { // no thumbnail image entry is found, // generate thumbnail image later return null; } // copy the thumbnail image data to cached folder FileUtils.ensureFileParent(cachedImageFile); OutputStream imageOutput = new FileOutputStream( cachedImageFile); try { byte[] buffer = new byte[2048]; int numInBuffer; while ((numInBuffer = sourceInput.read(buffer)) > 0) { imageOutput.write(buffer, 0, numInBuffer); } } finally { imageOutput.close(); } } finally { sourceInput.close(); } // use the cached thumbnail image file return cachedImageFile; } catch (Throwable e) { MindMapUIPlugin.getDefault().getLog().log(new Status( IStatus.WARNING, MindMapUIPlugin.PLUGIN_ID, "Failed to extract thumbnail image from source workbook file: " //$NON-NLS-1$ + sourceWorkbookFile, e)); // failed to extract thumbnail image, // generate thumbnail image later return null; } } private IStatus doGenerate(IProgressMonitor monitor, final IWorkbook workbook, final File targetImageFile, final String targetImagePath, final URI sourceWorkbookURI, final Properties imagePathMap) { if (isDisposed() || display.isDisposed()) return Status.CANCEL_STATUS; MindMapImageExporter exporter = new MindMapImageExporter(display); exporter.setSource(new MindMap(workbook.getPrimarySheet()), null, new Insets(MindMapUI.DEFAULT_EXPORT_MARGIN)); exporter.setResize(ResizeConstants.RESIZE_MAXPIXELS, 800, 600); exporter.setTargetFile(targetImageFile); exporter.export(); if (targetImageFile.isFile() && targetImageFile.canRead()) { imagePathMap.put(sourceWorkbookURI.toString(), targetImagePath); owner.saveImagePathMap(); return loadImage(targetImageFile); } return new Status(IStatus.ERROR, MindMapUIPlugin.PLUGIN_ID, "Failed to generate thumbnail image for template: " //$NON-NLS-1$ + sourceWorkbookURI.toString()); } private IStatus loadImage(File imageFile) { if (isDisposed() || display.isDisposed()) return Status.CANCEL_STATUS; final Image image = new Image(display, imageFile.getAbsolutePath()); if (isDisposed() || display.isDisposed()) { image.dispose(); return Status.CANCEL_STATUS; } display.asyncExec(new Runnable() { @Override public void run() { if (isDisposed()) { image.dispose(); return; } setImage(image); } }); return Status.OK_STATUS; } private void setImage(Image image) { Assert.isTrue(Display.getCurrent() != null, "Not in UI thread"); //$NON-NLS-1$ this.image = image; if (!isDisposed()) { owner.fireLabelProviderChanged( new LabelProviderChangedEvent(owner, template)); } } public void dispose() { Assert.isTrue(Display.getCurrent() != null, "Not in UI thread"); //$NON-NLS-1$ this.disposed = true; if (this.image != null) { this.image.dispose(); this.image = null; } } public Image getImage() { Assert.isTrue(Display.getCurrent() != null, "Not in UI thread"); //$NON-NLS-1$ return image; } public boolean isDisposed() { return disposed; } } public TemplateLabelProvider() { } @Override public boolean contains(ISchedulingRule rule) { return rule instanceof TemplateLabelProvider; } @Override public boolean isConflicting(ISchedulingRule rule) { return rule instanceof TemplateLabelProvider; } @Override public Image getImage(Object element) { if (!(element instanceof ITemplate)) return super.getImage(element); ITemplate template = (ITemplate) element; TemplateThumbnailImageLoader loader = imageLoaders.get(element); if (loader == null) { loader = new TemplateThumbnailImageLoader(this, template, Display.getCurrent()); imageLoaders.put(template, loader); } return loader.getImage(); } @Override public void dispose() { super.dispose(); Object[] loaderArray = imageLoaders.values().toArray(); imageLoaders.clear(); for (Object loader : loaderArray) { ((TemplateThumbnailImageLoader) loader).dispose(); } } private Properties getImagePathMap() { if (cachedImagePathMap == null) { Properties map = new Properties(); File mapFile = getImagePathMapFile(); if (mapFile.isFile() && mapFile.canRead()) { try { Reader reader = new BufferedReader(new FileReader(mapFile)); try { map.load(reader); } finally { reader.close(); } } catch (Throwable e) { MindMapUIPlugin.getDefault().getLog().log(new Status( IStatus.WARNING, MindMapUIPlugin.PLUGIN_ID, "Failed to load cached template thumbnail image path map from file: " //$NON-NLS-1$ + mapFile, e)); } } cachedImagePathMap = map; } return cachedImagePathMap; } private void saveImagePathMap() { Properties map = this.cachedImagePathMap; if (map == null) return; File mapFile = getImagePathMapFile(); FileUtils.ensureFileParent(mapFile); try { Writer writer = new BufferedWriter(new FileWriter(mapFile)); try { map.store(writer, null); } finally { writer.close(); } } catch (Throwable e) { MindMapUIPlugin.getDefault().getLog().log(new Status( IStatus.WARNING, MindMapUIPlugin.PLUGIN_ID, "Failed to save cached template thumbnail image path map to file: " //$NON-NLS-1$ + mapFile, e)); } } private static File getImagePathMapFile() { return new File(getCacheDirForTemplates(), "images.map"); //$NON-NLS-1$ } private static File getCacheDirForTemplates() { return MindMapUIPlugin.getDefault().getStateLocation() .append(CACHES_TEMPLATES_DIR).toFile(); } @Override public String getText(Object element) { if (element instanceof ITemplate) { ITemplate template = (ITemplate) element; return template.getName(); } return super.getText(element); } }