/*
* Copyright (C) 2012 - 2013 jonas.oreland@gmail.com
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.runnerup.export;
import android.annotation.TargetApi;
import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.util.Log;
import android.util.Pair;
import android.util.Patterns;
import org.json.JSONException;
import org.json.JSONObject;
import org.runnerup.common.util.Constants.DB;
import org.runnerup.export.format.TCX;
import org.runnerup.export.util.FormValues;
import org.runnerup.export.util.SyncHelper;
import org.runnerup.workout.Sport;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
@TargetApi(Build.VERSION_CODES.FROYO)
public class RuntasticSynchronizer extends DefaultSynchronizer {
public static final String NAME = "Runtastic";
public static final String BASE_URL = "https://www.runtastic.com";
public static final String START_URL = BASE_URL + "/en/login";
public static final String LOGIN_URL = BASE_URL + "/en/d/users/sign_in";
public static final String UPLOAD_URL = BASE_URL + "/import/upload_session";
public static final String UPDATE_SPORTS_TYPE = BASE_URL + "/import/update_sport_type";
long id = 0;
private String username = null;
private String password = null;
private Integer userId = null;
private String authToken = null;
static final Map<Integer, Sport> runtastic2sportMap = new HashMap<Integer, Sport>();
static final Map<Sport, Integer> sport2runtasticMap = new HashMap<Sport, Integer>();
static {
runtastic2sportMap.put(1, Sport.RUNNING);
runtastic2sportMap.put(3, Sport.BIKING);
for (Integer i : runtastic2sportMap.keySet()) {
sport2runtasticMap.put(runtastic2sportMap.get(i), i);
}
}
RuntasticSynchronizer(SyncManager syncManager) {
}
@Override
public long getId() {
return id;
}
@Override
public String getName() {
return NAME;
}
@Override
public void init(ContentValues config) {
id = config.getAsLong("_id");
String authToken = config.getAsString(DB.ACCOUNT.AUTH_CONFIG);
if (authToken != null) {
try {
JSONObject tmp = new JSONObject(authToken);
username = tmp.optString("username", null);
password = tmp.optString("password", null);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
@Override
public boolean isConfigured() {
if (username != null && password != null)
return true;
return false;
}
@Override
public void reset() {
username = null;
password = null;
userId = null;
authToken = null;
}
@Override
public String getAuthConfig() {
JSONObject tmp = new JSONObject();
try {
tmp.put("username", username);
tmp.put("password", password);
} catch (final JSONException e) {
e.printStackTrace();
}
return tmp.toString();
}
private void addRequestHeaders(HttpURLConnection conn) {
conn.addRequestProperty("Accept", "text/html, application/xhtml+xml, */*");
conn.addRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)");
conn.addRequestProperty("Accept-Encoding", "");
conn.addRequestProperty("Accept-Language", "en-US");
conn.addRequestProperty("Connection", "keep-alive");
conn.addRequestProperty("Origin", "https://www.runtastic.com");
conn.addRequestProperty("Referer", "https://www.runtastic.com");
addCookies(conn);
}
@Override
public Status connect() {
Exception ex = null;
HttpURLConnection conn = null;
formValues.clear();
Status s = Status.NEED_AUTH;
s.authMethod = AuthMethod.USER_PASS;
if (username == null || password == null) {
return s;
}
Log.i(getName(), "userId: " + userId + ", authToken: " + authToken);
if (userId != null && authToken != null) {
return Status.OK;
}
cookies.clear();
try {
/**
* connect to START_URL to get cookies/formValues
*/
conn = (HttpURLConnection) new URL(START_URL).openConnection();
conn.setInstanceFollowRedirects(false);
addRequestHeaders(conn);
{
// int responseCode = conn.getResponseCode();
// String amsg = conn.getResponseMessage();
getCookies(conn);
getFormValues(conn);
authToken = formValues.get("authenticity_token");
}
conn.disconnect();
if (authToken == null)
return Status.ERROR;
/**
* Then login using a post
*/
FormValues kv = new FormValues();
kv.put("user[email]", username);
kv.put("user[password]", password);
kv.put("authenticity-token", authToken);
kv.put("grant_type", "password");
conn = (HttpURLConnection) new URL(LOGIN_URL).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setDoOutput(true);
conn.setRequestMethod(RequestMethod.POST.name());
addRequestHeaders(conn);
conn.addRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
String url2 = null;
{
OutputStream wr = new BufferedOutputStream(
conn.getOutputStream());
kv.write(wr);
wr.flush();
wr.close();
// int responseCode = conn.getResponseCode();
// String amsg = conn.getResponseMessage();
getCookies(conn);
InputStream in = new BufferedInputStream(conn.getInputStream());
JSONObject ret = SyncHelper.parse(in);
if (ret != null && ret.has("success") && ret.getBoolean("success")) {
Matcher matcher = Patterns.WEB_URL.matcher(ret.getString("update"));
while (matcher.find()) {
String tmp = matcher.group();
final String users = "/users/";
if (tmp.contains(users)) {
int i = tmp.indexOf(users) + users.length();
int i2 = tmp.indexOf('/', i);
if (i2 > 0)
url2 = tmp.substring(0, i2);
else
url2 = tmp;
if (url2 != null)
break;
}
}
}
Log.i(getName(), "found url2: " + url2);
conn.disconnect();
}
if (url2 == null) {
return s;
}
{
url2 = url2 + "?authenticity_token=" + SyncHelper.URLEncode(authToken);
conn = (HttpURLConnection) new URL(url2).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setRequestMethod(RequestMethod.GET.name());
conn.setRequestProperty("Host", "www.runtastic.com");
conn.addRequestProperty("Accept", "application/json");
InputStream in = new BufferedInputStream(conn.getInputStream());
getCookies(conn);
JSONObject ret = SyncHelper.parse(in);
userId = ret.getJSONObject("user").getInt("id");
conn.disconnect();
}
if (userId != null && authToken != null) {
return Status.OK;
}
} catch (MalformedURLException e) {
ex = e;
} catch (IOException e) {
ex = e;
} catch (JSONException e) {
ex = e;
}
if (conn != null)
conn.disconnect();
s.ex = ex;
if (ex != null) {
ex.printStackTrace();
}
return s;
}
@Override
public Status upload(SQLiteDatabase db, long mID) {
Status s;
if ((s = connect()) != Status.OK) {
return s;
}
StringWriter writer = new StringWriter();
TCX tcx = new TCX(db);
HttpURLConnection conn = null;
try {
Pair<String, Sport> res = tcx.exportWithSport(mID, writer);
Sport sport = res.second;
String filename = String.format("activity%s%d.tcx", Long.toString(Math.round(1000 * Math.random())), mID);
String url = UPLOAD_URL + "?authenticity_token=" + SyncHelper.URLEncode(authToken) + "&qqfile=" +
filename;
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setDoOutput(true);
conn.setRequestMethod(RequestMethod.POST.name());
conn.addRequestProperty("Host", "www.runtastic.com");
conn.addRequestProperty("X-Requested-With", "XMLHttpRequest");
conn.addRequestProperty("X-File-Name", filename);
conn.addRequestProperty("Content-Type", "application/octet-stream");
addRequestHeaders(conn);
OutputStream out = new BufferedOutputStream(conn.getOutputStream());
out.write(writer.toString().getBytes());
out.flush();
out.close();
InputStream in = new BufferedInputStream(conn.getInputStream());
JSONObject ret = SyncHelper.parse(in);
getCookies(conn);
if (ret == null || !ret.has("success") || !ret.getBoolean("success")) {
Log.i(getName(), "ret: " + ret);
throw new JSONException("Unexpected json return");
}
String tmp = ret.getString("content");
final String name="name='";
int i1 = tmp.indexOf(name);
if (i1 < 0) {
Log.i(getName(), "ret: " + ret);
throw new JSONException("Unexpected json return");
}
i1 += name.length();
int i2 = tmp.indexOf('\'', i1);
String import_id = tmp.substring(i1, i2);
Log.i(getName(), "import_id: " + import_id);
conn.disconnect();
if (sport != null && sport != Sport.RUNNING && sport2runtasticMap.containsKey(sport)) {
conn = (HttpURLConnection) new URL(UPDATE_SPORTS_TYPE).openConnection();
conn.setDoOutput(true);
conn.setRequestMethod(RequestMethod.POST.name());
conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.addRequestProperty("X-Requested-With", "XMLHttpRequest");
addRequestHeaders(conn);
FormValues fv = new FormValues();
fv.put("authenticity_token", authToken);
fv.put("data_import_id", import_id);
fv.put("sport_type_id", sport2runtasticMap.get(sport).toString());
fv.put("user_id", userId.toString());
SyncHelper.postData(conn, fv);
int responseCode = conn.getResponseCode();
//String amsg = conn.getResponseMessage();
Log.i(getName(), "code: " + responseCode + ", html: " + getFormValues(conn));
conn.disconnect();
}
logout();
s = Status.OK;
s.activityId = mID;
return s;
} catch (IOException ex) {
s = Status.ERROR;
s.ex = ex;
} catch (JSONException e) {
s = Status.ERROR;
s.ex = e;
}
if (s.ex != null)
Log.e(getName(), "ex: " + s.ex);
if (conn != null)
conn.disconnect();
s.activityId = mID;
return s;
}
@Override
public boolean checkSupport(Feature f) {
switch (f) {
case UPLOAD:
return true;
case FEED:
case GET_WORKOUT:
case WORKOUT_LIST:
case LIVE:
case SKIP_MAP:
break;
}
return false;
}
@Override
public void logout() {
userId = null;
authToken = null;
super.logout();
}
}