package org.androad.osm.api.traces;
/**
* Copyright by Fabien Carrion
* This program is free software and licensed under GPL.
*
*/
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.util.List;
import org.osmdroid.contributor.util.RecordedGeoPoint;
import org.osmdroid.contributor.util.RecordedRouteGPXFormatter;
import org.androad.osm.api.traces.util.OSMTraceAPIConstants;
import org.androad.osm.util.constants.OSMConstants;
import org.androad.ui.common.CommonCallback;
import org.openstreetmap.api.exceptions.OSMAPIException;
/**
* Small java class that allows to upload gpx files to trailmapping.com via its api call.
*
* @author Fabien Carrion
*/
public class TrailmappingUploader implements OSMConstants, OSMTraceAPIConstants {
// ===========================================================
// Constants
// ===========================================================
private static final String GPX_UPLOAD_URL = "http://trailmapping.com/api/trips/create/";
private static final int BUFFER_SIZE = 65535;
private static final String BASE64_ENC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
private static final String BOUNDARY = "----------------------------d10f7aa230e8";
private static final String LINE_END = "\r\n";
private static final String DEFAULT_TITLE = OSM_CREATOR_INFO;
private static final String DEFAULT_BODY = OSM_CREATOR_INFO;
public static final SimpleDateFormat pseudoFileNameFormat = new SimpleDateFormat("yyyyMMdd'_'HHmmss'_'SSS");
// ===========================================================
// Fields
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
/**
* Title will be <code>DEFAULT_TITLE</code>
* NOTE: This method is not blocking!
* @param recordedGeoPointslist of GeoPoints.
*/
public static void uploadAsync(final List<RecordedGeoPoint> recordedGeoPoints, final String username, final String password, final CommonCallback<Void> pCallback) {
uploadAsync(recordedGeoPoints, username, password, DEFAULT_TITLE, DEFAULT_BODY, pseudoFileNameFormat.format(new GregorianCalendar().getTime()) + "_" + username + ".gpx", pCallback);
}
/**
* Title will be <code>DEFAULT_TITLE</code>
* NOTE: This method is blocking!
* @param recordedGeoPointslist of GeoPoints.
*/
public static void upload(final List<RecordedGeoPoint> recordedGeoPoints, final String username, final String password) throws OSMAPIException {
upload(recordedGeoPoints, username, password, DEFAULT_TITLE, DEFAULT_BODY, pseudoFileNameFormat.format(new GregorianCalendar().getTime()) + "_" + username + ".gpx");
}
/**
* NOTE: This method is not blocking! (Code runs in thread)
* @param username <code>not null</code> and <code>not empty</code>. Valid Trailmapping-username
* @param password <code>not null</code> and <code>not empty</code>. Valid password to the Trailmapping-username.
* @param title <code>not null</code>
* @param body if <code>not null</code>
* @param pseudoFileName ending with "<code>.gpx</code>"
* @param recordedGeoPointslist of GeoPoints.
*/
public static void uploadAsync(final List<RecordedGeoPoint> recordedGeoPoints, final String username, final String password, final String title, final String body, final String pseudoFileName, final CommonCallback<Void> pCallback) {
new Thread(new Runnable(){
@Override
public void run() {
try{
upload(recordedGeoPoints, username, password, title, body, pseudoFileName);
}catch(final Exception e){
pCallback.onFailure(e);
}
}
}, "TrailmappingUpload-Thread").start();
}
public static void upload(final List<RecordedGeoPoint> recordedGeoPoints, final String username, final String password, final String title, final String body, final String pseudoFileName) throws OSMAPIException {
if(username == null || username.length() == 0) {
return;
}
if(password == null || password.length() == 0) {
return;
}
if(title == null || title.length() == 0) {
return;
}
if(body == null || body.length() == 0) {
return;
}
if(pseudoFileName == null || !pseudoFileName.endsWith(".gpx")) {
return;
}
final InputStream gpxInputStream = new ByteArrayInputStream(RecordedRouteGPXFormatter.create(recordedGeoPoints).getBytes());
// Log.d(DEBUGTAG, "Uploading " + pseudoFileName + " to openstreetmap.org");
try {
final String urlTitle = (title == null) ? DEFAULT_TITLE : title.replaceAll("\\.;&?,/","_");
final String urlBody = (body == null) ? DEFAULT_BODY : body.replaceAll("\\\\.;&?,/","_");
final URL url = new URL(GPX_UPLOAD_URL);
// Log.d(DEBUGTAG, "Destination Url: " + url);
final HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(15000);
con.setRequestMethod("POST");
con.setDoOutput(true);
con.addRequestProperty("Authorization", "Basic " + encodeBase64(username + ":" + password));
con.addRequestProperty("Content-Type", "multipart/form-data; boundary="+BOUNDARY);
con.addRequestProperty("Connection", "close"); // counterpart of keep-alive
con.addRequestProperty("Expect", "");
con.connect();
final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(con.getOutputStream()));
// DataOutputStream out = new DataOutputStream(System.out);
writeContentDispositionFile(out, "gpx_file", gpxInputStream, pseudoFileName);
writeContentDisposition(out, "title", urlTitle);
writeContentDisposition(out, "body", urlBody);
writeContentDisposition(out, "public", "1");
out.writeBytes("--" + BOUNDARY + "--" + LINE_END);
out.flush();
final int retCode = con.getResponseCode();
String retMsg = con.getResponseMessage();
// Log.d(DEBUGTAG, "\nreturn code: "+retCode + " " + retMsg);
if (retCode != 200) {
// Look for a detailed error message from the server
if (con.getHeaderField("Error") != null) {
retMsg += "\n" + con.getHeaderField("Error");
}
con.disconnect();
throw new RuntimeException(retCode+" "+retMsg);
}
out.close();
con.disconnect();
} catch(final Exception e) {
throw new OSMAPIException(e);
}
}
/**
* @param out
* @param string
* @param gpxFile
* @throws IOException
*/
private static void writeContentDispositionFile(final DataOutputStream out, final String name, final InputStream gpxInputStream, final String pseudoFileName) throws IOException {
out.writeBytes("--" + BOUNDARY + LINE_END);
out.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + pseudoFileName + "\"" + LINE_END);
out.writeBytes("Content-Type: application/octet-stream" + LINE_END);
out.writeBytes(LINE_END);
final byte[] buffer = new byte[BUFFER_SIZE];
//int fileLen = (int)gpxFile.length();
int read;
int sumread = 0;
final InputStream in = new BufferedInputStream(gpxInputStream);
// Log.d(DEBUGTAG, "Transferring data to server");
while((read = in.read(buffer)) >= 0) {
out.write(buffer, 0, read);
out.flush();
sumread += read;
}
in.close();
out.writeBytes(LINE_END);
}
/**
* @param string
* @throws IOException
*/
private static void writeContentDisposition(final DataOutputStream out, final String name, final String value) throws IOException {
out.writeBytes("--" + BOUNDARY + LINE_END);
out.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"" + LINE_END);
out.writeBytes(LINE_END);
out.writeBytes(value + LINE_END);
}
private static String encodeBase64(final String s) {
final StringBuilder out = new StringBuilder();
for (int i = 0; i < (s.length()+2)/3; ++i) {
final int l = Math.min(3, s.length()-i*3);
final String buf = s.substring(i*3, i*3+l);
out.append(BASE64_ENC.charAt(buf.charAt(0)>>2));
out.append(BASE64_ENC.charAt((buf.charAt(0) & 0x03) << 4 | (l==1?0:(buf.charAt(1) & 0xf0) >> 4)));
out.append(l>1 ? BASE64_ENC.charAt((buf.charAt(1) & 0x0f) << 2 | (l==2 ? 0 : (buf.charAt(2) & 0xc0) >> 6)) : '=');
out.append(l>2 ? BASE64_ENC.charAt(buf.charAt(2) & 0x3f) : '=');
}
return out.toString();
}
}