package org.runnerup.export; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.util.Base64; import android.util.Log; import org.json.JSONException; import org.json.JSONObject; import org.runnerup.common.util.Constants; import org.runnerup.export.format.TCX; import org.runnerup.util.KXmlSerializer; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; import java.util.zip.GZIPOutputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; /** * Export integration with runningfreeonline.com. * @author jonfre */ public class RunningFreeOnlineSynchronizer extends DefaultSynchronizer { private long id = 0; private String username = null; private String secretKey = null; private boolean isConnected = false; public static final String NAME = "RunningFreeOnline"; private static final String BASE_URL = "http://www.runsaturday.com/runsaturday/SportTrackSync.asmx"; private static final String LOG_TAG = RunningFreeOnlineSynchronizer.class.getName(); public String getName() { return NAME; } @Override public long getId() { return id; } @Override public void init(ContentValues config) { id = config.getAsLong("_id"); final String authToken = config.getAsString(Constants.DB.ACCOUNT.AUTH_CONFIG); if (authToken != null) { try { JSONObject tmp = new JSONObject(authToken); username = tmp.optString("username", null); secretKey = tmp.optString("password", null); } catch (final JSONException e) { e.printStackTrace(); } } } @Override public Status connect() { Status retval = Status.NEED_AUTH; retval.authMethod = Synchronizer.AuthMethod.USER_PASS; if (username == null || secretKey == null) { return retval; } // Login by upload empty activity and check result for username/password error. Exception exception = null; HttpURLConnection conn = null; try { conn = createHttpURLConnection(); final BufferedWriter wr = new BufferedWriter(new PrintWriter(conn.getOutputStream())); createAndWriteSoapMessage(wr, ""); if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { InputStream in = new BufferedInputStream(conn.getInputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line = reader.readLine(); reader.close(); if (line != null && !line.contains("UserName and Password not matched")) { isConnected = true; return Synchronizer.Status.OK; } } } catch (MalformedURLException e) { exception = e; } catch (ProtocolException e) { exception = e; } catch (IOException e) { exception = e; } finally { if (conn != null) { conn.disconnect(); } } if (exception != null) { retval.ex = exception; Log.e(LOG_TAG, "connect failed", exception); } return retval; } /** * Create SOAP message for BASE_URL service and write result to wr. * @param wr * @param tcxData TCX data, gzipped and base64 encoded. * @throws IOException */ private void createAndWriteSoapMessage(BufferedWriter wr, String tcxData) throws IOException { final KXmlSerializer mXML = new KXmlSerializer(); mXML.setOutput(wr); mXML.startDocument("UTF-8", true); mXML.startTag("", "soap:Envelope"); mXML.attribute("", "xmlns:soap", "http://www.w3.org/2003/05/soap-envelope"); mXML.attribute("", "xmlns:run", "http://www.runsaturday.com"); mXML.startTag("", "soap:Header"); mXML.startTag("", "run:SportTrackCredentials"); mXML.startTag("", "run:UserName"); mXML.text(username); mXML.endTag("", "run:UserName"); mXML.startTag("", "run:SecretKey"); mXML.text(secretKey); mXML.endTag("", "run:SecretKey"); mXML.endTag("", "run:SportTrackCredentials"); mXML.endTag("", "soap:Header"); mXML.startTag("", "soap:Body"); mXML.startTag("", "run:Upload"); mXML.startTag("", "run:compressedData"); mXML.text(tcxData); mXML.endTag("", "run:compressedData"); mXML.startTag("", "run:skipDuplicates"); mXML.text("true"); mXML.endTag("", "run:skipDuplicates"); mXML.endTag("", "run:Upload"); mXML.endTag("", "soap:Body"); mXML.endTag("", "soap:Envelope"); mXML.endDocument(); mXML.flush(); } @Override public boolean isConfigured() { return username != null && secretKey != null; } @Override public String getAuthConfig() { JSONObject tmp = new JSONObject(); try { tmp.put("username", username); tmp.put("password", secretKey); } catch (final JSONException e) { e.printStackTrace(); } return tmp.toString(); } @Override public void reset() { username = null; secretKey = null; isConnected = false; } @Override public Status upload(SQLiteDatabase db, long mID) { Status retval = Status.ERROR; Exception exception = null; HttpURLConnection conn = null; try { TCX tcx = new TCX(db); StringWriter writer = new StringWriter(); tcx.exportWithSport(mID, writer); byte[] gzippedTcx = gzip(writer.toString()); conn = createHttpURLConnection(); final BufferedWriter wr = new BufferedWriter(new PrintWriter(conn.getOutputStream())); createAndWriteSoapMessage(wr,Base64.encodeToString(gzippedTcx, Base64.NO_WRAP)); if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { throw new Exception("Server response code " + conn.getResponseCode()); } final InputStream in = new BufferedInputStream(conn.getInputStream()); final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); final DocumentBuilder dob = dbf.newDocumentBuilder(); final InputSource is = new InputSource(); is.setByteStream(in); final Document doc = dob.parse(is); conn.disconnect(); conn = null; NodeList nodes = doc.getElementsByTagName("Success"); if (nodes != null && nodes.getLength() == 1) { if ("true".equals(nodes.item(0).getTextContent())) { retval = Status.OK; } else { String errorMessage = null; nodes = doc.getElementsByTagName("ErrorMessage"); if (nodes != null && nodes.getLength() == 1) { errorMessage = nodes.item(0).getTextContent(); } Log.e(LOG_TAG, String.format("Upload failed; Service said '%s'", errorMessage)); } } } catch (MalformedURLException e) { exception = e; } catch (ProtocolException e) { exception = e; } catch (IOException e) { exception = e; } catch (ParserConfigurationException e) { exception = e; } catch (SAXException e) { exception = e; } catch (Exception e) { exception = e; } finally { if (conn != null) { conn.disconnect(); } } if (exception != null) { retval.ex = exception; Log.e(LOG_TAG, "upload failed", exception); } retval.activityId = mID; return retval; } /** * Create HttpURLConnection for BASE_URL service upload operation. * @return * @throws IOException */ private HttpURLConnection createHttpURLConnection() throws IOException { HttpURLConnection conn = (HttpURLConnection) new URL(BASE_URL).openConnection(); conn.setDoOutput(true); conn.setRequestMethod(RequestMethod.POST.name()); conn.addRequestProperty("Content-Type", "text/xml; charset=utf-8"); conn.addRequestProperty("SOAPAction", "http://www.runsaturday.com/Upload"); return conn; } public static byte[] gzip(String string) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(string.length()); GZIPOutputStream gos = new GZIPOutputStream(os); gos.write(string.getBytes()); gos.close(); byte[] compressed = os.toByteArray(); os.close(); return compressed; } @Override public boolean checkSupport(Synchronizer.Feature f) { switch (f) { case UPLOAD: return true; default: return false; } } }