2013-04-18 23:35:02 +02:00
|
|
|
package net.osmand.map;
|
|
|
|
|
|
|
|
import java.io.BufferedInputStream;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
2015-02-11 08:47:34 +01:00
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.OutputStream;
|
2015-07-07 11:51:51 +02:00
|
|
|
import java.lang.ref.WeakReference;
|
2013-04-18 23:35:02 +02:00
|
|
|
import java.net.URL;
|
|
|
|
import java.net.URLConnection;
|
|
|
|
import java.net.UnknownHostException;
|
|
|
|
import java.util.ArrayList;
|
2014-11-08 01:40:36 +01:00
|
|
|
import java.util.Collection;
|
2013-04-18 23:35:02 +02:00
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashSet;
|
2014-11-08 01:40:36 +01:00
|
|
|
import java.util.Iterator;
|
2015-07-07 11:51:51 +02:00
|
|
|
import java.util.LinkedList;
|
2013-04-18 23:35:02 +02:00
|
|
|
import java.util.List;
|
|
|
|
import java.util.Set;
|
2014-11-08 01:40:36 +01:00
|
|
|
import java.util.concurrent.ArrayBlockingQueue;
|
|
|
|
import java.util.concurrent.BlockingDeque;
|
|
|
|
import java.util.concurrent.BlockingQueue;
|
|
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
2013-04-18 23:35:02 +02:00
|
|
|
import java.util.concurrent.ThreadPoolExecutor;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
import net.osmand.PlatformUtil;
|
2015-02-20 13:58:16 +01:00
|
|
|
import net.osmand.osm.io.NetworkUtils;
|
2013-04-18 23:35:02 +02:00
|
|
|
import net.osmand.util.Algorithms;
|
|
|
|
|
|
|
|
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;
|
2013-10-27 19:17:12 +01:00
|
|
|
public static final long TIMEOUT_AFTER_EXCEEDING_LIMIT_ERRORS = 15000;
|
2014-03-08 15:50:07 +01:00
|
|
|
public static final int TILE_DOWNLOAD_MAX_ERRORS_PER_TIMEOUT = 50;
|
2014-03-11 09:45:40 +01:00
|
|
|
private static final int CONNECTION_TIMEOUT = 30000;
|
2013-04-18 23:35:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
private static MapTileDownloader downloader = null;
|
|
|
|
private static Log log = PlatformUtil.getLog(MapTileDownloader.class);
|
|
|
|
|
|
|
|
public static String USER_AGENT = "OsmAnd~";
|
|
|
|
|
|
|
|
|
|
|
|
private ThreadPoolExecutor threadPoolExecutor;
|
2015-07-07 11:51:51 +02:00
|
|
|
private List<WeakReference<IMapDownloaderCallback>> callbacks = new LinkedList<WeakReference<IMapDownloaderCallback>>();
|
2013-04-18 23:35:02 +02:00
|
|
|
|
2014-10-08 21:50:49 +02:00
|
|
|
private Set<File> pendingToDownload;
|
2013-04-18 23:35:02 +02:00
|
|
|
private Set<File> currentlyDownloaded;
|
|
|
|
|
|
|
|
private int currentErrors = 0;
|
|
|
|
private long timeForErrorCounter = 0;
|
|
|
|
|
|
|
|
|
|
|
|
public static MapTileDownloader getInstance(String userAgent){
|
|
|
|
if(downloader == null){
|
|
|
|
downloader = new MapTileDownloader(TILE_DOWNLOAD_THREADS);
|
|
|
|
if(userAgent != null) {
|
|
|
|
MapTileDownloader.USER_AGENT = userAgent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
2015-10-09 10:35:21 +02:00
|
|
|
public String referer = null;
|
2013-04-18 23:35:02 +02:00
|
|
|
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;
|
|
|
|
}
|
2015-02-11 08:47:34 +01:00
|
|
|
|
|
|
|
public void saveTile(InputStream inputStream) throws IOException {
|
|
|
|
fileToSave.getParentFile().mkdirs();
|
|
|
|
OutputStream stream = null;
|
|
|
|
try {
|
|
|
|
stream = new FileOutputStream(fileToSave);
|
|
|
|
Algorithms.streamCopy(inputStream, stream);
|
|
|
|
stream.flush();
|
|
|
|
} finally {
|
|
|
|
Algorithms.closeStream(inputStream);
|
|
|
|
Algorithms.closeStream(stream);
|
|
|
|
}
|
|
|
|
}
|
2013-04-18 23:35:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public MapTileDownloader(int numberOfThreads){
|
2014-11-08 01:40:36 +01:00
|
|
|
|
2013-04-18 23:35:02 +02:00
|
|
|
threadPoolExecutor = new ThreadPoolExecutor(numberOfThreads, numberOfThreads, TILE_DOWNLOAD_SECONDS_TO_WORK,
|
2014-11-08 01:40:36 +01:00
|
|
|
TimeUnit.SECONDS, createQueue());
|
2013-04-18 23:35:02 +02:00
|
|
|
// 1.6 method but very useful to kill non-running threads
|
|
|
|
// threadPoolExecutor.allowCoreThreadTimeOut(true);
|
2014-10-08 21:50:49 +02:00
|
|
|
pendingToDownload = Collections.synchronizedSet(new HashSet<File>());
|
2013-04-18 23:35:02 +02:00
|
|
|
currentlyDownloaded = Collections.synchronizedSet(new HashSet<File>());
|
|
|
|
|
|
|
|
}
|
2014-11-08 01:40:36 +01:00
|
|
|
|
|
|
|
protected BlockingQueue<Runnable> createQueue() {
|
|
|
|
boolean loaded = false;
|
|
|
|
try {
|
|
|
|
Class<?> cl = Class.forName("java.util.concurrent.LinkedBlockingDeque");
|
|
|
|
loaded = cl != null;
|
|
|
|
} catch (Throwable e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
if(!loaded) {
|
|
|
|
// for Android 2.2
|
|
|
|
return new LinkedBlockingQueue<Runnable>();
|
|
|
|
}
|
|
|
|
return createDeque();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected static BlockingQueue<Runnable> createDeque() {
|
|
|
|
return new net.osmand.util.LIFOBlockingDeque<Runnable>();
|
|
|
|
}
|
2013-04-18 23:35:02 +02:00
|
|
|
|
|
|
|
public void addDownloaderCallback(IMapDownloaderCallback callback){
|
2015-07-07 11:51:51 +02:00
|
|
|
LinkedList<WeakReference<IMapDownloaderCallback>> ncall = new LinkedList<WeakReference<IMapDownloaderCallback>>(callbacks);
|
|
|
|
ncall.add(new WeakReference<MapTileDownloader.IMapDownloaderCallback>(callback));
|
|
|
|
callbacks = ncall;
|
2013-04-18 23:35:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void removeDownloaderCallback(IMapDownloaderCallback callback){
|
2015-07-07 11:51:51 +02:00
|
|
|
LinkedList<WeakReference<IMapDownloaderCallback>> ncall = new LinkedList<WeakReference<IMapDownloaderCallback>>(callbacks);
|
|
|
|
Iterator<WeakReference<IMapDownloaderCallback>> it = ncall.iterator();
|
|
|
|
while(it.hasNext()) {
|
|
|
|
IMapDownloaderCallback c = it.next().get();
|
|
|
|
if(c == callback) {
|
|
|
|
it.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
callbacks = ncall;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void clearCallbacks() {
|
|
|
|
callbacks = new LinkedList<WeakReference<IMapDownloaderCallback>>();
|
2013-04-18 23:35:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public List<IMapDownloaderCallback> getDownloaderCallbacks() {
|
2015-07-07 11:51:51 +02:00
|
|
|
ArrayList<IMapDownloaderCallback> lst = new ArrayList<IMapDownloaderCallback>();
|
|
|
|
for(WeakReference<IMapDownloaderCallback> c : callbacks) {
|
|
|
|
IMapDownloaderCallback ct = c.get();
|
|
|
|
if(ct != null) {
|
|
|
|
lst.add(ct);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return lst;
|
2013-04-18 23:35:02 +02:00
|
|
|
}
|
|
|
|
|
2014-10-08 21:50:49 +02:00
|
|
|
public boolean isFilePendingToDownload(File f){
|
|
|
|
return pendingToDownload.contains(f);
|
|
|
|
}
|
|
|
|
|
2013-04-18 23:35:02 +02:00
|
|
|
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(){
|
|
|
|
// That's very strange because exception in impl of queue (possibly wrong impl)
|
|
|
|
// threadPoolExecutor.getQueue().clear();
|
|
|
|
while(!threadPoolExecutor.getQueue().isEmpty()){
|
|
|
|
threadPoolExecutor.getQueue().poll();
|
|
|
|
}
|
2014-10-13 00:58:01 +02:00
|
|
|
pendingToDownload.clear();
|
2013-04-18 23:35:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void requestToDownload(DownloadRequest request){
|
|
|
|
long now = System.currentTimeMillis();
|
|
|
|
if((int)(now - timeForErrorCounter) > TIMEOUT_AFTER_EXCEEDING_LIMIT_ERRORS ) {
|
|
|
|
timeForErrorCounter = now;
|
|
|
|
currentErrors = 0;
|
|
|
|
} else if(currentErrors > TILE_DOWNLOAD_MAX_ERRORS_PER_TIMEOUT){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(request.url == null){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-10-08 21:50:49 +02:00
|
|
|
if (!isFileCurrentlyDownloaded(request.fileToSave)
|
|
|
|
&& !isFilePendingToDownload(request.fileToSave)) {
|
|
|
|
pendingToDownload.add(request.fileToSave);
|
2013-04-18 23:35:02 +02:00
|
|
|
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) {
|
2014-10-13 00:58:01 +02:00
|
|
|
pendingToDownload.remove(request.fileToSave);
|
2013-04-18 23:35:02 +02:00
|
|
|
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();
|
2015-02-11 08:47:34 +01:00
|
|
|
request.setError(false);
|
2013-04-18 23:35:02 +02:00
|
|
|
try {
|
2015-02-20 13:58:16 +01:00
|
|
|
URLConnection connection = NetworkUtils.getHttpURLConnection(request.url);
|
2013-04-18 23:35:02 +02:00
|
|
|
connection.setRequestProperty("User-Agent", USER_AGENT); //$NON-NLS-1$
|
2015-10-09 10:35:21 +02:00
|
|
|
if(request.referer != null)
|
|
|
|
connection.setRequestProperty("Referer", request.referer); //$NON-NLS-1$
|
2014-03-08 15:50:07 +01:00
|
|
|
connection.setConnectTimeout(CONNECTION_TIMEOUT);
|
|
|
|
connection.setReadTimeout(CONNECTION_TIMEOUT);
|
2013-04-18 23:35:02 +02:00
|
|
|
BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream(), 8 * 1024);
|
2015-02-11 08:47:34 +01:00
|
|
|
request.saveTile(inputStream);
|
2013-04-18 23:35:02 +02:00
|
|
|
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++;
|
2013-10-27 19:17:12 +01:00
|
|
|
timeForErrorCounter = System.currentTimeMillis();
|
2013-04-18 23:35:02 +02:00
|
|
|
request.setError(true);
|
|
|
|
log.error("UnknownHostException, cannot download tile " + request.url + " " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
|
|
|
|
} catch (IOException e) {
|
|
|
|
currentErrors++;
|
2013-10-27 19:17:12 +01:00
|
|
|
timeForErrorCounter = System.currentTimeMillis();
|
2013-04-18 23:35:02 +02:00
|
|
|
request.setError(true);
|
|
|
|
log.warn("Cannot download tile : " + request.url, e); //$NON-NLS-1$
|
|
|
|
} finally {
|
|
|
|
currentlyDownloaded.remove(request.fileToSave);
|
|
|
|
}
|
|
|
|
if (!request.error) {
|
2015-07-07 11:51:51 +02:00
|
|
|
fireLoadCallback(request);
|
2013-04-18 23:35:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int compareTo(DownloadMapWorker o) {
|
|
|
|
return 0; //(int) (time - o.time);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2015-07-07 11:51:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
public void fireLoadCallback(DownloadRequest request) {
|
|
|
|
Iterator<WeakReference<IMapDownloaderCallback>> it = callbacks.iterator();
|
|
|
|
while(it.hasNext()) {
|
|
|
|
IMapDownloaderCallback c = it.next().get();
|
2015-07-29 20:00:42 +02:00
|
|
|
if(c != null) {
|
2015-07-07 11:51:51 +02:00
|
|
|
c.tileDownloaded(request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-04-18 23:35:02 +02:00
|
|
|
}
|