/* * Copyright (c) 2012 Socialize Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.socialize.ui.image; import com.socialize.log.SocializeLogger; import com.socialize.util.*; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; /** * @author Jason Polites * */ public class ImageLoadAsyncTask extends Thread { private Queue<ImageLoadRequest> requests; private Map<String, ImageLoadRequest> requestsInProcess; private boolean running = false; private SocializeLogger logger; private ImageUrlLoader imageUrlLoader; private DrawableCache cache; private Drawables drawables; private Base64Utils base64Utils; public ImageLoadAsyncTask() { super("ImageLoadAsyncTask"); } @Override public void run() { if(requests != null) { while(running) { while (!requests.isEmpty() && running) { if(logger != null && logger.isDebugEnabled()) { logger.debug("ImageLoadAsyncTask has [" + requests.size() + "] images to load"); } ImageLoadRequest request = requests.poll(); String url = request.getUrl(); if(!request.isCanceled()) { if(logger != null && logger.isDebugEnabled()) { logger.debug("ImageLoadAsyncTask found image to load at: " + url); } try { SafeBitmapDrawable drawable = null; if(cache != null) { drawable = cache.get(url); } if(drawable == null || drawable.isRecycled()) { switch (request.getType()) { case ENCODED: if(logger != null && logger.isDebugEnabled()) { logger.debug("ImageLoadAsyncTask image loading from encoded data for: " + url); } drawable = (SafeBitmapDrawable) drawables.getDrawableFromUrl(url, base64Utils.decode(request.getEncodedImageData()), request.getScaleWidth(), request.getScaleHeight()); break; default: if(logger != null && logger.isDebugEnabled()) { logger.debug("ImageLoadAsyncTask image loading from remote url for: " + url); } drawable = loadImageFromUrl(url, request.getScaleWidth(), request.getScaleHeight()); break; } if(drawable != null) { cache.put(url, (CacheableDrawable) drawable, false); } } int notified = request.notifyListeners(drawable); if(logger != null && logger.isDebugEnabled()) { logger.debug("Notified [" + notified + "] listeners for image load of url [" + url + "]"); } } catch (Exception e) { request.notifyListeners(e); } finally { requestsInProcess.remove(url); } } else { requestsInProcess.remove(url); if(logger != null && logger.isDebugEnabled()) { logger.debug("ImageLoadAsyncTask request canceled for " + request.getUrl()); } } } synchronized(this) { if(running) { try { doWait(); } catch (InterruptedException ignore) {} } } } } } protected CacheableDrawable loadImageFromUrl(String url, int width, int height) throws Exception { return imageUrlLoader.loadImageFromUrl(url, width, height); } public void cancel(String url) { if(requestsInProcess != null) { ImageLoadRequest request = requestsInProcess.get(url); if(request != null) { request.setCanceled(true); } } } public synchronized void enqueue(ImageLoadRequest request) { if(isRunning()) { String url = request.getUrl(); ImageLoadRequest current = requestsInProcess.get(url); if(current != null && !current.isCanceled() && !current.isListenersNotified()) { if(logger != null && logger.isDebugEnabled()) { logger.debug("Image with url [" + url + "] already being loaded. Adding listener to queue on current request [" + current.getUrl() + "]"); } current.merge(request); notifyAll(); } else { requests.add(request); requestsInProcess.put(url, request); notifyAll(); } } else { if(logger != null) { logger.warn("Image load task is not running. Enqueue request ignored"); } } } public void init() { requests = makeRequests(); requestsInProcess = makePendingRequests(); } protected void onStart() { init(); running = true; setDaemon(true); } public void start() { onStart(); super.start(); } // So we can mock protected Queue<ImageLoadRequest> makeRequests() { return new ConcurrentLinkedQueue<ImageLoadRequest>(); } // So we can mock protected Map<String, ImageLoadRequest> makePendingRequests() { return new ConcurrentHashMap<String, ImageLoadRequest>(); } // So we can mock protected void doWait() throws InterruptedException { wait(); } public synchronized void finish() { running = false; if(requests != null) { requests.clear(); } if(requests != null) { requestsInProcess.clear(); } notifyAll(); } public boolean isRunning() { return running; } public void setLogger(SocializeLogger logger) { this.logger = logger; } public void setImageUrlLoader(ImageUrlLoader imageUrlLoader) { this.imageUrlLoader = imageUrlLoader; } public void setCache(DrawableCache cache) { this.cache = cache; } public void setDrawables(Drawables drawables) { this.drawables = drawables; } public void setBase64Utils(Base64Utils base64Utils) { this.base64Utils = base64Utils; } public boolean isEmpty() { return requestsInProcess.isEmpty(); } public boolean isLoading(String url) { return requestsInProcess.containsKey(url); } }