Project: Anki-Android
/***************************************************************************************
 * Copyright (c) 2009 Edu Zamora <[email protected]>                                   * 
 *                                                                                      * 
 * 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; 
    } 
 
}