/******************************************************************************* * Copyright (c) 2011 GitHub Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Kevin Sawicki (GitHub Inc.) - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.internal.github.ui; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.URL; import java.net.URLConnection; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageLoader; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; /** * Stores Avatars */ public class AvatarStore implements Serializable, ISchedulingRule { /** * Callback interface for avatar image data loaded */ public interface IAvatarCallback { /** * Avatar loaded * * @param data * @param store */ void loaded(ImageData data, AvatarStore store); } /** * serialVersionUID */ private static final long serialVersionUID = -7791322302733784441L; /** * TIMEOUT */ public static final int TIMEOUT = 30 * 1000; /** * BUFFER_SIZE */ public static final int BUFFER_SIZE = 8192; private Map<String, byte[]> avatars = new HashMap<String, byte[]>(); /** * Get cached avatar image data * * @param url * @return image data or null if not present in store */ public ImageData getAvatar(String url) { ImageData data = null; url = normalize(url); if (url != null) { byte[] cached = this.avatars.get(url); if (cached != null) data = getData(cached); } return data; } /** * Normalize url removing query parameters * * @param url * @return normalized */ protected String normalize(String url) { try { URL parsed = new URL(url); parsed = new URL(parsed.getProtocol(), parsed.getHost(), parsed.getPath()); return parsed.toExternalForm(); } catch (IOException e) { return null; } } /** * Load avatar * * @param url * @param callback */ public void loadAvatar(final String url, final IAvatarCallback callback) { Job job = new Job(MessageFormat.format("Loading avatar for {0}", url)) { protected IStatus run(IProgressMonitor monitor) { try { ImageData data = loadAvatar(url); if (data != null) callback.loaded(data, AvatarStore.this); } catch (IOException ignore) { // Ignored } return Status.OK_STATUS; } }; job.schedule(); } /** * Load avatar image data from url * * @param url * @return avatar image data * @throws IOException */ public ImageData loadAvatar(String url) throws IOException { url = normalize(url); URL parsed = new URL(url); byte[] data = this.avatars.get(url); if (data != null) return getData(data); URLConnection connection = parsed.openConnection(); connection.setConnectTimeout(TIMEOUT); ByteArrayOutputStream output = new ByteArrayOutputStream(); InputStream input = connection.getInputStream(); try { byte[] buffer = new byte[BUFFER_SIZE]; int read = input.read(buffer); while (read != -1) { output.write(buffer, 0, read); read = input.read(buffer); } } finally { try { input.close(); } catch (IOException ignore) { // Ignored } try { output.close(); } catch (IOException ignore) { // Ignored } } data = output.toByteArray(); this.avatars.put(url, data); return getData(data); } /** * Get scaled image * * @param size * @param data * @return image data scaled to specified size */ public Image getScaledImage(int size, ImageData data) { Display display = PlatformUI.getWorkbench().getDisplay(); Image image = new Image(display, data); Rectangle sourceBounds = image.getBounds(); // Return original image and don't scale if size matches request if (sourceBounds.width == size) return image; Image scaled = new Image(display, size, size); GC gc = new GC(scaled); try { gc.setAntialias(SWT.ON); gc.setInterpolation(SWT.HIGH); Rectangle targetBounds = scaled.getBounds(); gc.drawImage(image, 0, 0, sourceBounds.width, sourceBounds.height, 0, 0, targetBounds.width, targetBounds.height); } finally { gc.dispose(); image.dispose(); } return scaled; } /** * Get avatar image data * * @param bytes * @return image data */ public ImageData getData(byte[] bytes) { ByteArrayInputStream stream = new ByteArrayInputStream(bytes); try { ImageData[] images = new ImageLoader().load(stream); if (images.length > 0) return images[0]; } finally { try { stream.close(); } catch (IOException ignore) { // Ignored } } return null; } /** * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule) */ public boolean contains(ISchedulingRule rule) { return this == rule; } /** * @see org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.core.runtime.jobs.ISchedulingRule) */ public boolean isConflicting(ISchedulingRule rule) { return this == rule; } }