com.ichi2.anki.AnkiDroidProxy.java Source code

Java tutorial

Introduction

Here is the source code for com.ichi2.anki.AnkiDroidProxy.java

Source

/***************************************************************************************
 * Copyright (c) 2009 Edu Zamora <edu.zasu@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 com.ichi2.anki;

import android.util.Log;

import com.ichi2.utils.Base64;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.InflaterInputStream;

public class AnkiDroidProxy {

    /**
     * Connection settings
     */
    // anki.ichi2.net hosted at 78.46.104.28
    public static final String SYNC_HOST = "anki.ichi2.net";
    public static final String SYNC_URL = "http://" + SYNC_HOST + "/sync/";
    public static final String SYNC_SEARCH = "http://" + SYNC_HOST + "/file/search";

    /**
     * Synchronization.
     */
    public static final int LOGIN_OK = 0;
    public static final int LOGIN_INVALID_USER_PASS = 1;

    /**
     * Shared deck's fields
     */
    private static final int SD_ID = 0;
    private static final int SD_USERNAME = 1;
    private static final int SD_TITLE = 2;
    private static final int SD_DESCRIPTION = 3;
    private static final int SD_TAGS = 4;
    private static final int SD_VERSION = 5;
    private static final int SD_FACTS = 6;
    private static final int SD_SIZE = 7;
    private static final int SD_COUNT = 8;
    private static final int SD_MODIFIED = 9;
    private static final int SD_FNAME = 10;

    /**
     * List to hold the shared decks
     */
    private static List<SharedDeck> sSharedDecks;

    private String mUsername;
    private String mPassword;
    private String mDeckName;

    private JSONObject mDecks;
    private double mTimestamp;

    public AnkiDroidProxy(String user, String password) {
        mUsername = user;
        mPassword = password;
        mDeckName = "";
        mDecks = null;
    }

    public void setDeckName(String deckName) {
        mDeckName = deckName;
    }

    public double getTimestamp() {
        return mTimestamp;
    }

    public int connect() {
        if (mDecks == null) {
            String decksString = getDecks();
            try {
                JSONObject jsonDecks = new JSONObject(decksString);
                if ("OK".equalsIgnoreCase(jsonDecks.getString("status"))) {
                    mDecks = jsonDecks.getJSONObject("decks");
                    Log.i(AnkiDroidApp.TAG, "Server decks = " + mDecks.toString());
                    mTimestamp = jsonDecks.getDouble("timestamp");
                    Log.i(AnkiDroidApp.TAG, "Server timestamp = " + mTimestamp);
                    return LOGIN_OK;
                } else if ("invalidUserPass".equalsIgnoreCase(jsonDecks.getString("status"))) {
                    return LOGIN_INVALID_USER_PASS;
                }
            } catch (JSONException e) {
                Log.i(AnkiDroidApp.TAG, "JSONException = " + e.getMessage());
            }
        }

        return LOGIN_OK;
    }

    public boolean hasDeck(String name) {
        connect();
        Iterator decksIterator = mDecks.keys();
        while (decksIterator.hasNext()) {
            String serverDeckName = (String) decksIterator.next();
            if (name.equalsIgnoreCase(serverDeckName)) {
                return true;
            }
        }

        return false;
    }

    public double modified() {
        double lastModified = 0;

        connect();
        try {
            JSONArray deckInfo = mDecks.getJSONArray(mDeckName);
            lastModified = deckInfo.getDouble(0);
        } catch (JSONException e) {
            Log.i(AnkiDroidApp.TAG, "JSONException = " + e.getMessage());
        }

        return lastModified;
    }

    public double lastSync() {
        double lastSync = 0;

        connect();
        try {
            JSONArray deckInfo = mDecks.getJSONArray(mDeckName);
            lastSync = deckInfo.getDouble(1);
        } catch (JSONException e) {
            Log.i(AnkiDroidApp.TAG, "JSONException = " + e.getMessage());
        }
        return lastSync;
    }

    public String getDecks() {
        // Log.i(AnkiDroidApp.TAG, "getDecks - user = " + username + ", password = " + password);
        String decksServer = "{}";

        try {
            // FIXME: Client is hardcoded.
            String data = "p=" + URLEncoder.encode(mPassword, "UTF-8") + "&client=ankidroid-0.4&u="
                    + URLEncoder.encode(mUsername, "UTF-8") + "&d=None&sources=" + URLEncoder.encode("[]", "UTF-8")
                    + "&libanki=0.9.9.8.6&pversion=5";

            // Log.i(AnkiDroidApp.TAG, "Data json = " + data);
            HttpPost httpPost = new HttpPost(SYNC_URL + "getDecks");
            StringEntity entity = new StringEntity(data);
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept-Encoding", "identity");
            httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response = httpClient.execute(httpPost);
            Log.i(AnkiDroidApp.TAG, "Response = " + response.toString());
            HttpEntity entityResponse = response.getEntity();
            Log.i(AnkiDroidApp.TAG, "Entity's response = " + entityResponse.toString());
            InputStream content = entityResponse.getContent();
            Log.i(AnkiDroidApp.TAG, "Content = " + content.toString());
            decksServer = Utils.convertStreamToString(new InflaterInputStream(content));
            Log.i(AnkiDroidApp.TAG, "String content = " + decksServer);

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            Log.i(AnkiDroidApp.TAG, "ClientProtocolException = " + e.getMessage());
        } catch (IOException e) {
            Log.i(AnkiDroidApp.TAG, "IOException = " + e.getMessage());
        }

        return decksServer;
    }

    public List<String> getPersonalDecks() {
        ArrayList<String> personalDecks = new ArrayList<String>();

        connect();

        Iterator decksIterator = mDecks.keys();
        while (decksIterator.hasNext()) {
            personalDecks.add((String) decksIterator.next());
        }

        return personalDecks;
    }

    public void createDeck(String name) {
        Log.i(AnkiDroidApp.TAG, "createDeck");
        // Log.i(AnkiDroidApp.TAG, "user = " + username + ", password = " + password);

        try {
            String data = "p=" + URLEncoder.encode(mPassword, "UTF-8") + "&u="
                    + URLEncoder.encode(mUsername, "UTF-8") + "&d=None&name=" + URLEncoder.encode(name, "UTF-8");

            // Log.i(AnkiDroidApp.TAG, "Data json = " + data);
            HttpPost httpPost = new HttpPost(SYNC_URL + "createDeck");
            StringEntity entity = new StringEntity(data);
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept-Encoding", "identity");
            httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response = httpClient.execute(httpPost);
            Log.i(AnkiDroidApp.TAG, "Response = " + response.toString());
            HttpEntity entityResponse = response.getEntity();
            Log.i(AnkiDroidApp.TAG, "Entity's response = " + entityResponse.toString());
            InputStream content = entityResponse.getContent();
            Log.i(AnkiDroidApp.TAG, "Content = " + content.toString());
            Log.i(AnkiDroidApp.TAG,
                    "String content = " + Utils.convertStreamToString(new InflaterInputStream(content)));

            // Add created deck to the list of decks on server
            mDecks.put(name, new JSONArray("[0,0]"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            Log.i(AnkiDroidApp.TAG, "ClientProtocolException = " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            Log.i(AnkiDroidApp.TAG, "IOException = " + e.getMessage());
            e.printStackTrace();
        } catch (JSONException e) {
            Log.i(AnkiDroidApp.TAG, "JSONException = " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * Anki Desktop -> libanki/anki/sync.py, HttpSyncServerProxy - summary
     * 
     * @param lastSync
     */
    public JSONObject summary(double lastSync) {

        Log.i(AnkiDroidApp.TAG, "Summary Server");

        // Log.i(AnkiDroidApp.TAG, "user = " + username + ", password = " + password + ", lastSync = " + lastSync);
        JSONObject summaryServer = new JSONObject();

        try {
            // FIXME: Try to do the connection without encoding the lastSync in Base 64
            String data = "p=" + URLEncoder.encode(mPassword, "UTF-8") + "&u="
                    + URLEncoder.encode(mUsername, "UTF-8") + "&d=" + URLEncoder.encode(mDeckName, "UTF-8")
                    + "&lastSync="
                    + URLEncoder.encode(
                            Base64.encodeBytes(
                                    Utils.compress(String.format(Utils.ENGLISH_LOCALE, "%f", lastSync).getBytes())),
                            "UTF-8")
                    + "&base64=" + URLEncoder.encode("true", "UTF-8");

            // Log.i(AnkiDroidApp.TAG, "Data json = " + data);
            HttpPost httpPost = new HttpPost(SYNC_URL + "summary");
            StringEntity entity = new StringEntity(data);
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept-Encoding", "identity");
            httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response = httpClient.execute(httpPost);
            Log.i(AnkiDroidApp.TAG, "Response = " + response.toString());
            HttpEntity entityResponse = response.getEntity();
            Log.i(AnkiDroidApp.TAG, "Entity's response = " + entityResponse.toString());
            InputStream content = entityResponse.getContent();
            Log.i(AnkiDroidApp.TAG, "Content = " + content.toString());
            summaryServer = new JSONObject(Utils.convertStreamToString(new InflaterInputStream(content)));
            Log.i(AnkiDroidApp.TAG, "Summary server = ");
            Utils.printJSONObject(summaryServer);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            Log.i(AnkiDroidApp.TAG, "ClientProtocolException = " + e.getMessage());
        } catch (IOException e) {
            Log.i(AnkiDroidApp.TAG, "IOException = " + e.getMessage());
        } catch (JSONException e) {
            Log.i(AnkiDroidApp.TAG, "JSONException = " + e.getMessage());
        }

        return summaryServer;
    }

    /**
     * Anki Desktop -> libanki/anki/sync.py, HttpSyncServerProxy - applyPayload
     *
     * @param payload
     */
    public JSONObject applyPayload(JSONObject payload) {
        Log.i(AnkiDroidApp.TAG, "applyPayload");
        // Log.i(AnkiDroidApp.TAG, "user = " + username + ", password = " + password + ", payload = " +
        // payload.toString());
        JSONObject payloadReply = new JSONObject();

        try {
            // FIXME: Try to do the connection without encoding the payload in Base 64
            String data = "p=" + URLEncoder.encode(mPassword, "UTF-8") + "&u="
                    + URLEncoder.encode(mUsername, "UTF-8") + "&d=" + URLEncoder.encode(mDeckName, "UTF-8")
                    + "&payload="
                    + URLEncoder.encode(Base64.encodeBytes(Utils.compress(payload.toString().getBytes())), "UTF-8")
                    + "&base64=" + URLEncoder.encode("true", "UTF-8");

            // Log.i(AnkiDroidApp.TAG, "Data json = " + data);
            HttpPost httpPost = new HttpPost(SYNC_URL + "applyPayload");
            StringEntity entity = new StringEntity(data);
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept-Encoding", "identity");
            httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response = httpClient.execute(httpPost);
            Log.i(AnkiDroidApp.TAG, "Response = " + response.toString());
            HttpEntity entityResponse = response.getEntity();
            Log.i(AnkiDroidApp.TAG, "Entity's response = " + entityResponse.toString());
            InputStream content = entityResponse.getContent();
            Log.i(AnkiDroidApp.TAG, "Content = " + content.toString());
            String contentString = Utils.convertStreamToString(new InflaterInputStream(content));
            Log.i(AnkiDroidApp.TAG, "Payload response = ");
            payloadReply = new JSONObject(contentString);
            Utils.printJSONObject(payloadReply, false);
            Utils.saveJSONObject(payloadReply);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            Log.i(AnkiDroidApp.TAG, "ClientProtocolException = " + e.getMessage());
        } catch (IOException e) {
            Log.i(AnkiDroidApp.TAG, "IOException = " + e.getMessage());
        } catch (JSONException e) {
            Log.i(AnkiDroidApp.TAG, "JSONException = " + e.getMessage());
        }

        return payloadReply;
    }

    /**
     * Get shared decks.
     */
    public static List<SharedDeck> getSharedDecks() throws Exception {

        try {
            if (sSharedDecks == null) {
                sSharedDecks = new ArrayList<SharedDeck>();

                HttpGet httpGet = new HttpGet(SYNC_SEARCH);
                httpGet.setHeader("Accept-Encoding", "identity");
                httpGet.setHeader("Host", SYNC_HOST);
                DefaultHttpClient defaultHttpClient = new DefaultHttpClient();

                HttpResponse httpResponse = defaultHttpClient.execute(httpGet);
                String response = Utils.convertStreamToString(httpResponse.getEntity().getContent());
                // Log.i(AnkiDroidApp.TAG, "Content = " + response);
                sSharedDecks.addAll(getSharedDecksListFromJSONArray(new JSONArray(response)));
            }
        } catch (Exception e) {
            sSharedDecks = null;
            throw new Exception();
        }

        return sSharedDecks;
    }

    private static List<SharedDeck> getSharedDecksListFromJSONArray(JSONArray jsonSharedDecks)
            throws JSONException {
        List<SharedDeck> sharedDecks = new ArrayList<SharedDeck>();

        if (jsonSharedDecks != null) {
            // Log.i(AnkiDroidApp.TAG, "Number of shared decks = " + jsonSharedDecks.length());

            int nbDecks = jsonSharedDecks.length();
            for (int i = 0; i < nbDecks; i++) {
                JSONArray jsonSharedDeck = jsonSharedDecks.getJSONArray(i);

                SharedDeck sharedDeck = new SharedDeck();
                sharedDeck.setId(jsonSharedDeck.getInt(SD_ID));
                sharedDeck.setUsername(jsonSharedDeck.getString(SD_USERNAME));
                sharedDeck.setTitle(jsonSharedDeck.getString(SD_TITLE));
                sharedDeck.setDescription(jsonSharedDeck.getString(SD_DESCRIPTION));
                sharedDeck.setTags(jsonSharedDeck.getString(SD_TAGS));
                sharedDeck.setVersion(jsonSharedDeck.getInt(SD_VERSION));
                sharedDeck.setFacts(jsonSharedDeck.getInt(SD_FACTS));
                sharedDeck.setSize(jsonSharedDeck.getInt(SD_SIZE));
                sharedDeck.setCount(jsonSharedDeck.getInt(SD_COUNT));
                sharedDeck.setModified(jsonSharedDeck.getDouble(SD_MODIFIED));
                sharedDeck.setFileName(jsonSharedDeck.getString(SD_FNAME));

                // sharedDeck.prettyLog();

                sharedDecks.add(sharedDeck);
            }
        }

        return sharedDecks;
    }

}