org.runnerup.export.RuntasticSynchronizer.java Source code

Java tutorial

Introduction

Here is the source code for org.runnerup.export.RuntasticSynchronizer.java

Source

/*
 * 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();
    }
}