/*
This file is part of OpenSatNav.
OpenSatNav is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenSatNav is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenSatNav. If not, see <http://www.gnu.org/licenses/>.
*/
// Created by plusminus on 21:31:36 - 25.09.2008
package org.andnav.osm.views.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.andnav.osm.util.constants.OpenStreetMapConstants;
import org.andnav.osm.views.util.OpenStreetMapTileFilesystemProvider.TileMetaData;
import org.andnav.osm.views.util.constants.OpenStreetMapViewConstants;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
*
* @author Nicolas Gramlich
*
*/
public class OpenStreetMapTileDownloader implements OpenStreetMapConstants, OpenStreetMapViewConstants {
// ===========================================================
// Constants
// ===========================================================
public static final int MAPTILEDOWNLOADER_SUCCESS_ID = 0;
public static final int MAPTILEDOWNLOADER_FAIL_ID = MAPTILEDOWNLOADER_SUCCESS_ID + 1;
private static final String ETAG_HEADER = "ETag";
private static final String IF_NONE_MATCH_HEADER = "If-None-Match";
// ===========================================================
// Fields
// ===========================================================
protected Set<String> mPending = Collections.synchronizedSet(new HashSet<String>());
protected Context mCtx;
protected OpenStreetMapTileFilesystemProvider mMapTileFSProvider;
protected OpenStreetMapTileCache mMapTileCache;
protected ExecutorService mThreadPool = Executors.newFixedThreadPool(5);
// ===========================================================
// Constructors
// ===========================================================
public OpenStreetMapTileDownloader(final Context ctx, final OpenStreetMapTileFilesystemProvider aMapTileFSProvider,
final OpenStreetMapTileCache aMapTileCache){
this.mCtx = ctx;
this.mMapTileFSProvider = aMapTileFSProvider;
this.mMapTileCache = aMapTileCache;
}
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
/** Sets the Child-ImageView of this to the URL passed. */
public void getRemoteImageAsync(final String aURLString, final Handler callback, final TileMetaData tileMetaData) {
this.mThreadPool.execute(new Runnable(){
@Override
public void run() {
InputStream in = null;
OutputStream out = null;
try {
if(DEBUGMODE)
Log.i(DEBUGTAG, "Downloading Maptile from url: " + aURLString);
URL url = new URL(aURLString);
HttpURLConnection conn = (HttpURLConnection)url.openConnection(); // doesn't actually open connection?
String userAgent = HttpUserAgentHelper.getUserAgent(mCtx);
if (userAgent != null)
conn.setRequestProperty("User-Agent", userAgent);
if (tileMetaData != null) {
Date ifModifiedSince = tileMetaData.getDateAdded();
if (ifModifiedSince != null) {
if (DEBUGMODE)
Log.d(DEBUGTAG, "Adding ifModifiedSince header: " + ifModifiedSince);
conn.setIfModifiedSince(ifModifiedSince.getTime());
}
String etag = tileMetaData.getEtag();
if (etag != null) {
if (DEBUGMODE)
Log.d(DEBUGTAG, "Adding If-None-Match header: " + etag);
conn.addRequestProperty(IF_NONE_MATCH_HEADER, etag);
}
}
in = new BufferedInputStream(conn.getInputStream(), StreamUtils.IO_BUFFER_SIZE);
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
out = new BufferedOutputStream(dataStream, StreamUtils.IO_BUFFER_SIZE);
StreamUtils.copy(in, out);
out.flush();
final byte[] data = dataStream.toByteArray();
if (DEBUGMODE)
Log.d(DEBUGTAG, "Maptile " + aURLString + " got response: " + conn.getResponseMessage());
switch (conn.getResponseCode()) {
case HttpURLConnection.HTTP_OK:
final TileMetaData metadata = new TileMetaData(conn.getHeaderField(ETAG_HEADER));
OpenStreetMapTileDownloader.this.mMapTileFSProvider.saveFile(aURLString, metadata, data);
OpenStreetMapTileDownloader.this.mMapTileCache.clearTile(aURLString);
if (DEBUGMODE)
Log.d(DEBUGTAG, "Maptile saved to: " + aURLString);
final Message successMessage = Message.obtain(callback, MAPTILEDOWNLOADER_SUCCESS_ID);
successMessage.sendToTarget();
break;
case HttpURLConnection.HTTP_NOT_MODIFIED:
if (DEBUGMODE)
Log.d(DEBUGTAG, "Maptile " + aURLString + " not modified from stored version");
OpenStreetMapTileDownloader.this.mMapTileFSProvider.updateFile(aURLString);
break;
default:
throw new RuntimeException("Bad HTTP response code: " + conn.getResponseCode() +
" msg: " + conn.getResponseMessage() + " getting tile: " + aURLString);
}
} catch (Exception e) {
final Message failMessage = Message.obtain(callback, MAPTILEDOWNLOADER_FAIL_ID);
failMessage.sendToTarget();
Log.e(DEBUGTAG, "Error Downloading MapTile. Exception: " + e.getClass().getSimpleName(), e);
} finally {
StreamUtils.closeStream(in);
StreamUtils.closeStream(out);
}
OpenStreetMapTileDownloader.this.mPending.remove(aURLString);
}
});
}
public void requestMapTileAsync(final String aURLString, final Handler callback, final TileMetaData metadata) {
if(this.mPending.contains(aURLString))
return;
this.mPending.add(aURLString);
getRemoteImageAsync(aURLString, callback, metadata);
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}