MapTileDownloader.java :  » Map » osmand » net » osmand » data » preparation » Android Open Source

Android Open Source » Map » osmand 
osmand » net » osmand » data » preparation » MapTileDownloader.java
package net.osmand.data.preparation;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import net.osmand.Algoritms;
import net.osmand.LogUtil;
import net.osmand.Version;

import org.apache.commons.logging.Log;


public class MapTileDownloader {
  // Download manager tile settings
  public static int TILE_DOWNLOAD_THREADS = 4;
  public static int TILE_DOWNLOAD_SECONDS_TO_WORK = 25;
  public static final int TILE_DOWNLOAD_MAX_ERRORS = -1;
  
  private static MapTileDownloader downloader = null;
  private static Log log = LogUtil.getLog(MapTileDownloader.class);
  
  
  private ThreadPoolExecutor threadPoolExecutor;
  private List<IMapDownloaderCallback> callbacks = new ArrayList<IMapDownloaderCallback>();
  
  private Set<File> currentlyDownloaded;
  
  private int currentErrors = 0;
  
  
  
  
  public static MapTileDownloader getInstance(){
    if(downloader == null){
      downloader = new MapTileDownloader(TILE_DOWNLOAD_THREADS);
    }
    return downloader;
  }
  
  /**
   * Callback for map downloader 
   */
  public interface IMapDownloaderCallback {

    /**
     * Sometimes null cold be passed as request
     * That means that there were a lot of requests but
     * once method is called 
     * (in order to not create a collection of request & reduce calling times) 
     * @param fileSaved
     */
    public void tileDownloaded(DownloadRequest request);
  }
  
  /**
   * Download request could subclassed to create own detailed request 
   */
  public static class DownloadRequest {
    public final File fileToSave;
    public final int zoom;
    public final int xTile;
    public final int yTile;
    public final String url;
    public boolean error;
    
    public DownloadRequest(String url, File fileToSave, int xTile, int yTile, int zoom) {
      this.url = url;
      this.fileToSave = fileToSave;
      this.xTile = xTile;
      this.yTile = yTile;
      this.zoom = zoom;
    }
    
    public DownloadRequest(String url, File fileToSave) {
      this.url = url;
      this.fileToSave = fileToSave;
      xTile = -1;
      yTile = -1;
      zoom = -1;
    }
    
    public void setError(boolean error){
      this.error = error;
    }
  }
  
  
  public MapTileDownloader(int numberOfThreads){
    threadPoolExecutor = new ThreadPoolExecutor(numberOfThreads, numberOfThreads, TILE_DOWNLOAD_SECONDS_TO_WORK, 
        TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    // 1.6 method but very useful to kill non-running threads
//    threadPoolExecutor.allowCoreThreadTimeOut(true);
    currentlyDownloaded = Collections.synchronizedSet(new HashSet<File>());
    
  }
  
  public void addDownloaderCallback(IMapDownloaderCallback callback){
    callbacks.add(callback);
  }
  
  public void removeDownloaderCallback(IMapDownloaderCallback callback){
    callbacks.remove(callback);
  }
  
  public List<IMapDownloaderCallback> getDownloaderCallbacks() {
    return callbacks;
  }
  
  public boolean isFileCurrentlyDownloaded(File f){
    return currentlyDownloaded.contains(f);
  }
  
  public boolean isSomethingBeingDownloaded(){
    return !currentlyDownloaded.isEmpty();
  }
  
  public int getRemainingWorkers(){
    return (int) (threadPoolExecutor.getTaskCount());
  }
  
  public void refuseAllPreviousRequests(){
    //FIXME it could cause NPE in android implementation think about different style
    // That's very strange because exception in impl of queue (possibly wrong impl)
//    threadPoolExecutor.getQueue().clear();
    while(!threadPoolExecutor.getQueue().isEmpty()){
      threadPoolExecutor.getQueue().poll();
    }
  }
  
  public void requestToDownload(DownloadRequest request){
    if(TILE_DOWNLOAD_MAX_ERRORS > 0 && 
        currentErrors > TILE_DOWNLOAD_MAX_ERRORS){
      return;
    }
    if(request.url == null){
      return;
    }
    
    if (!isFileCurrentlyDownloaded(request.fileToSave)) {
      threadPoolExecutor.execute(new DownloadMapWorker(request));
    }
  }
  
  
  private class DownloadMapWorker implements Runnable, Comparable<DownloadMapWorker> {
    
    private DownloadRequest request;
    
    private DownloadMapWorker(DownloadRequest request){
      this.request = request;
    }
    
    @Override
    public void run() {
      if (request != null && request.fileToSave != null && request.url != null) {
        if(currentlyDownloaded.contains(request.fileToSave)){
          return;
        }
        
        currentlyDownloaded.add(request.fileToSave);
        if(log.isDebugEnabled()){
          log.debug("Start downloading tile : " + request.url); //$NON-NLS-1$
        }
        long time = System.currentTimeMillis();
        try {
          request.fileToSave.getParentFile().mkdirs();
          URL url = new URL(request.url);
          URLConnection connection = url.openConnection();
          connection.setRequestProperty("User-Agent", Version.APP_NAME_VERSION); //$NON-NLS-1$
          connection.setConnectTimeout(35000);
          BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream(), 8 * 1024);
          FileOutputStream stream = null;
          try {
            stream = new FileOutputStream(request.fileToSave);
            Algoritms.streamCopy(inputStream, stream);
            stream.flush();
          } finally {
            Algoritms.closeStream(inputStream);
            Algoritms.closeStream(stream);
          }
          if (log.isDebugEnabled()) {
            log.debug("Downloading tile : " + request.url + " successfull " + (System.currentTimeMillis() - time) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
          }
        } catch (UnknownHostException e) {
          currentErrors++;
          request.setError(true);
          log.error("UnknownHostException, cannot download tile " + request.url + " " + e.getMessage()); //$NON-NLS-1$  //$NON-NLS-2$
        } catch (IOException e) {
          currentErrors++;
          request.setError(true);
          log.warn("Cannot download tile : " + request.url, e); //$NON-NLS-1$
        } finally {
          currentlyDownloaded.remove(request.fileToSave);
        }
        for(IMapDownloaderCallback c : new ArrayList<IMapDownloaderCallback>(callbacks)){
          c.tileDownloaded(request);
        }
      }
        
    } 
    
    @Override
    public int compareTo(DownloadMapWorker o) {
      return 0; //(int) (time - o.time);
    }
    
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.