org.maikelwever.droidpile.backend.ApiConnecter.java Source code

Java tutorial

Introduction

Here is the source code for org.maikelwever.droidpile.backend.ApiConnecter.java

Source

/*
 * This file is part of Droidpile: a Mailpile client for Android.
 *
 * You should have recieved a copy of the LICENSE file along with Droidpile.
 * If not: see <https://github.com/maikelwever/droidpile/blob/master/LICENSE>
 */
package org.maikelwever.droidpile.backend;

import android.content.Context;
import android.util.Log;

import com.activeandroid.query.Select;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.maikelwever.droidpile.helpers.ContextToSettings;
import org.maikelwever.droidpile.models.ApiSearchResponse;
import org.maikelwever.droidpile.models.MailpileAttachment;
import org.maikelwever.droidpile.models.MailpileMessage;
import org.maikelwever.droidpile.models.ParsedMailpileMessage;
import org.maikelwever.droidpile.sugarmodels.DataCache;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.net.ssl.HttpsURLConnection;

/**
 * Class to provide access to Mailpile api interface.
 * Returns useful lists and objects.
 */
public class ApiConnecter {
    public static final int CACHE_TIMEOUT = 5; // minutes
    private static final String ENCODING = "UTF-8";
    private final String baseUrl;
    private final String[] allowed_endpoints = { "contact", "filter", "help", "message", "page", "searchable",
            "settings", "shownetwork", "tag", "search" };

    private Context context;
    private boolean useCache;
    private String basicAuth = "";

    public ApiConnecter(Context context) {
        this.context = context;
        this.baseUrl = ContextToSettings.contextToApiSettings(context);
        this.useCache = ContextToSettings.contextToUseCacheBoolean(context);
        this.basicAuth = ContextToSettings.contextToBasicAuthString(context);
    }

    /*
    public ApiConnecter(String baseUrl) {
    this.context = MainActivity.getContext();
    this.baseUrl = baseUrl;
    this.useCache = ContextToSettings.contextToUseCacheBoolean(context);
    this.basicAuth = ContextToSettings.contextToBasicAuthString(context);
    }
    */

    public String constructUrlQuery(String... kvs) {
        ArrayList<NameValuePair> args = new ArrayList<NameValuePair>();
        for (int i = 0; i < kvs.length; i = i + 2) {
            args.add(new BasicNameValuePair(
                    //URLEncoder.encode(kvs[i], ENCODING),
                    //URLEncoder.encode(kvs[i+1], ENCODING)
                    kvs[i], kvs[i + 1]));
        }
        return URLEncodedUtils.format(args, ENCODING);
    }

    public String getData(String... data) throws IOException, JSONException {
        return this.getData(true, data);
    }

    public String getData(boolean cache, String... data) throws IOException, JSONException {

        if (data.length < 1) {
            throw new IllegalArgumentException("More than 1 argument is required.");
        }

        boolean foundOne = false;
        for (String endpoint : this.allowed_endpoints) {
            if (endpoint.equals(data[0])) {
                foundOne = true;
                break;
            }
        }

        if (!foundOne) {
            throw new IllegalArgumentException("Endpoint name is not in 'allowed_endpoints'.");
        }

        String url = this.baseUrl;
        String suffix = "";
        for (String arg : data) {
            suffix += "/" + arg;
        }

        Log.d("droidpile", "URL we will call: " + url + suffix);

        String json = null;
        if (this.useCache) {
            json = this.getCachedRecordForQuery(suffix);
        }

        if (json == null || json.isEmpty()) {
            url += suffix;
            URL uri = new URL(url);
            InputStreamReader is;

            if (url.startsWith("https")) {
                HttpsURLConnection con = (HttpsURLConnection) uri.openConnection();
                if (!this.basicAuth.isEmpty()) {
                    con.setRequestProperty("Authorization", basicAuth);
                }
                is = new InputStreamReader(con.getInputStream(), ENCODING);
            } else {
                HttpURLConnection con = (HttpURLConnection) uri.openConnection();
                if (!this.basicAuth.isEmpty()) {
                    con.setRequestProperty("Authorization", basicAuth);
                }
                is = new InputStreamReader(con.getInputStream(), ENCODING);
            }

            StringBuilder sb = new StringBuilder();
            BufferedReader br = new BufferedReader(is);
            String read = br.readLine();

            while (read != null) {
                sb.append(read);
                read = br.readLine();
            }
            String stringData = sb.toString();

            if (this.useCache) {
                this.storeInCache(suffix, stringData);
            }

            return stringData;
        } else {
            return json;
        }
    }

    public String getCachedRecordForQuery(String query) {
        String jsonToReturn = "";
        try {
            List<DataCache> currentlyCached = new Select().from(DataCache.class).where("key = ?", query).execute();
            for (DataCache record : currentlyCached) {
                long diff = new Date().getTime() - record.retrievedOn;

                long diffMinutes = diff / (60 * 1000) % 60;
                long diffHours = diff / (60 * 60 * 1000) % 24;
                long diffDays = diff / (24 * 60 * 60 * 1000);

                if (diffHours > 0 || diffDays > 0 || diffMinutes > CACHE_TIMEOUT) {
                    record.delete();
                } else {
                    jsonToReturn = record.data;
                    Log.d("droidpile", String.format("Cache hit for query: %s\nAge is %d hours %d minutes", query,
                            diffHours, diffMinutes));
                    break;
                }
            }
        } catch (Exception e) {
            Log.d("droidpile", Log.getStackTraceString(e));
        }
        return jsonToReturn;
    }

    public void storeInCache(String query, String data) {
        try {
            List<DataCache> currentlyCached = new Select().from(DataCache.class).where("key = ?", query).execute();
            for (DataCache dc : currentlyCached) {
                dc.delete();
            }
        } catch (Exception e) {
            Log.d("droidpile", "No old cache entries to clear.");
            Log.d("droidpile", Log.getStackTraceString(e));
        }

        DataCache object = new DataCache(query, data, new Date().getTime());
        object.save();
        Log.d("droidpile", "Stored request in cache.");
    }

    private static final String remoteTotalKeyName = "remoteTotalMessages";

    public void setRemoteTotal(int total) {
        try {
            List<DataCache> currentlyCached = new Select().from(DataCache.class)
                    .where("key = ?", remoteTotalKeyName).execute();
            for (DataCache dc : currentlyCached) {
                dc.delete();
            }
        } catch (Exception e) {
            Log.d("droidpile", "No old cache entries to clear.");
            Log.d("droidpile", Log.getStackTraceString(e));
        }

        DataCache object = new DataCache();
        object.key = remoteTotalKeyName;
        object.data = Integer.toString(total);
        object.retrievedOn = new Date().getTime();
        object.save();
        Log.d("droidpile", "Stored total in cache. New total: " + Integer.toString(total));
    }

    public int getRemoteTotal() {
        int total = getRemoteTotal(true);
        Log.d("droidpile", "Current remote total:" + Integer.toString(total));
        return total;
    }

    public int getRemoteTotal(boolean dbg) {
        int total = 0;
        try {
            List<DataCache> currentlyCached = new Select().from(DataCache.class)
                    .where("key = ?", remoteTotalKeyName).execute();
            for (DataCache dc : currentlyCached) {
                if (total == 0) {
                    total = Integer.parseInt(dc.data);
                }
                dc.delete();
            }
        } catch (Exception e) {
            Log.d("droidpile", "No old cache entries to clear.");
            Log.d("droidpile", Log.getStackTraceString(e));
            return total;
        }
        return total;
    }

    public void getAndPrettyprint(String... args) {
        String out = "";
        for (String arg : args) {
            out += "/" + arg;
        }
        out += "\n";
        try {
            JSONObject obj = new JSONObject(this.getData(args));

            Iterator<String> it = obj.keys();
            while (it.hasNext()) {
                String key = it.next();
                out += key + ":\t" + obj.get(key).toString() + "\n";
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.print(out);
    }

    public String postData(ArrayList<NameValuePair> payload, String... data) throws IOException {
        if (data.length < 1) {
            throw new IllegalArgumentException("More than 1 argument is required.");
        }

        String url = this.baseUrl;
        String suffix = "";
        for (String arg : data) {
            suffix += "/" + arg;
        }

        suffix += "/";

        Log.d("droidpile", "URL we will call: " + url + suffix);

        url += suffix;
        URL uri = new URL(url);
        InputStreamReader is;
        HttpURLConnection con;

        if (url.startsWith("https")) {
            con = (HttpsURLConnection) uri.openConnection();
        } else {
            con = (HttpURLConnection) uri.openConnection();
        }

        String urlParameters = URLEncodedUtils.format(payload, ENCODING);

        con.setDoInput(true);
        con.setDoOutput(true);
        con.setInstanceFollowRedirects(false);
        con.setRequestMethod("POST");
        con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        con.setRequestProperty("charset", ENCODING);
        con.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes(ENCODING).length));
        con.setUseCaches(false);

        DataOutputStream wr = new DataOutputStream(con.getOutputStream());
        wr.writeBytes(urlParameters);
        wr.flush();
        wr.close();
        is = new InputStreamReader(con.getInputStream(), ENCODING);

        StringBuilder sb = new StringBuilder();
        BufferedReader br = new BufferedReader(is);
        String read = br.readLine();

        while (read != null) {
            sb.append(read);
            read = br.readLine();
        }
        String stringData = sb.toString();
        con.disconnect();
        return stringData;
    }

    // ---------------------------------------
    // API endpoints, wrapped for convenience.
    // ---------------------------------------

    public ApiSearchResponse search(int start, int end, String query) throws IOException, JSONException {
        ArrayList<MailpileMessage> list = new ArrayList<MailpileMessage>();
        int totalItemCount = 0;
        ArrayList<NameValuePair> args = new ArrayList<NameValuePair>();
        args.add(new BasicNameValuePair("q", query));
        args.add(new BasicNameValuePair("start", Integer.toString(start)));
        args.add(new BasicNameValuePair("end", Integer.toString(end)));

        String data = this.getData("search", "?" + URLEncodedUtils.format(args, ENCODING));
        JSONObject result = new JSONObject(data).getJSONObject("result");
        JSONArray threadIds = result.getJSONArray("thread_ids");
        JSONObject metadata = result.getJSONObject("data").getJSONObject("metadata");

        totalItemCount = result.getJSONObject("stats").getInt("total");

        for (int i = 0; i < threadIds.length(); i++) {
            String threadid = threadIds.getString(i);
            JSONObject message = metadata.getJSONObject(threadid);
            if (message != null) {
                list.add(parseJsonIntoMessage(message, threadid));
            }
        }

        return new ApiSearchResponse(list, totalItemCount);
    }

    private MailpileMessage parseJsonIntoMessage(JSONObject messageData, String threadId) throws JSONException {
        String subject = messageData.getString("subject");
        JSONObject from = messageData.getJSONObject("from");
        return new MailpileMessage(subject, messageData.getJSONObject("body").getString("snippet"),
                from.getString("address"), from.getString("fn"), threadId,
                new Date(messageData.getLong("timestamp") * 1000L));
    }

    public ParsedMailpileMessage getMessage(String messageId) {
        try {
            String data = this.getData("message", "=" + messageId + "/");
            JSONObject result = new JSONObject(data).getJSONObject("result");
            JSONObject jsonData = result.getJSONObject("data");
            JSONObject messageData = jsonData.getJSONObject("messages").getJSONObject(messageId);
            JSONObject metadataJson = jsonData.getJSONObject("metadata").getJSONObject(messageId);
            JSONObject addressesJson = jsonData.getJSONObject("addresses");

            MailpileMessage metaData = parseJsonIntoMessage(metadataJson, messageId);

            JSONArray htmlPartsArray = messageData.getJSONArray("html_parts");
            String htmlMessage = "";
            JSONArray textPartsArray = messageData.getJSONArray("text_parts");
            String textMessage = "";

            if (htmlPartsArray.length() > 0) {
                JSONObject htmlPart = htmlPartsArray.getJSONObject(0);
                //htmlMessage = new String(htmlPart.getString("data").getBytes(), "UTF-8");
                htmlMessage = htmlPart.getString("data");
            } else {
                for (int i = 0; i < textPartsArray.length(); i++) {
                    JSONObject tp = (JSONObject) textPartsArray.get(i);
                    //textMessage = new String(textPart.getString("data").getBytes(), "UTF-8");
                    if (!textMessage.isEmpty()) {
                        textMessage += "\n\n";
                    }
                    textMessage += tp.getString("data");
                }
                htmlMessage = textMessage.replace("\n", "<br>").replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
            }

            String fromString = "" + metaData.fromName;
            if (!metaData.fromEmail.equals(metaData.fromName)) {
                fromString += " (" + metaData.fromEmail + ")";
            }

            JSONArray toAids = metadataJson.getJSONArray("to_aids");
            String toString = "";
            for (int x = 0; x < toAids.length(); x++) {
                JSONObject currentAddress = addressesJson.getJSONObject(toAids.getString(x));
                toString += currentAddress.getString("fn");
                if (!currentAddress.getString("fn").equals(currentAddress.getString("address"))) {
                    toString += " (" + currentAddress.getString("address") + ")";
                }
                if (x != toAids.length() - 1) {
                    toString += ", ";
                }
            }

            JSONArray ccAids = metadataJson.getJSONArray("cc_aids");
            String ccString = "";
            for (int x = 0; x < ccAids.length(); x++) {
                JSONObject currentAddress = addressesJson.getJSONObject(ccAids.getString(x));
                ccString += currentAddress.getString("fn");
                if (!currentAddress.getString("fn").equals(currentAddress.getString("address"))) {
                    ccString += " (" + currentAddress.getString("address") + ")";
                }
                if (x != ccAids.length() - 1) {
                    ccString += ", ";
                }
            }

            ParsedMailpileMessage message = new ParsedMailpileMessage(metaData, htmlMessage, textMessage,
                    fromString, toString, ccString, result.getJSONArray("thread_ids"));

            JSONArray attachmentsJson = messageData.getJSONArray("attachments");
            if (attachmentsJson.length() > 0) {
                for (int x = 0; x < attachmentsJson.length(); x++) {
                    try {
                        JSONObject attachment = attachmentsJson.getJSONObject(x);

                        String contentId = attachment.getString("content-id");
                        int count = attachment.getInt("count");
                        String filename = attachment.getString("filename");
                        int length = attachment.getInt("length");
                        String mimetype = attachment.getString("mimetype");
                        String part = attachment.getString("part");

                        message.addAttachment(
                                new MailpileAttachment(contentId, count, filename, length, mimetype, part));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            return message;
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("droidpile", "Error while retrieving message", e);
            return null;
        }
    }

    /* POST API endpoints */
    public String createDraft() {
        try {
            JSONObject obj = new JSONObject(this.postData(new ArrayList<NameValuePair>(), "message", "compose"));
            JSONArray createdMessages = obj.getJSONObject("result").getJSONArray("created");
            if (createdMessages.length() > 0) {
                return createdMessages.getString(0);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "";
    }

    private ArrayList<NameValuePair> getNameValues(String mID, String to, String cc, String subject, String body) {
        ArrayList<NameValuePair> payload = new ArrayList<NameValuePair>();
        payload.add(new BasicNameValuePair("mid", mID));
        payload.add(new BasicNameValuePair("subject", subject));
        payload.add(new BasicNameValuePair("body", body));
        payload.add(new BasicNameValuePair("to", to));
        payload.add(new BasicNameValuePair("cc", cc));
        return payload;
    }

    public boolean saveDraft(String mID, String to, String cc, String subject, String body) {
        try {
            JSONObject response = new JSONObject(
                    this.postData(getNameValues(mID, to, cc, subject, body), "message", "update"));

            if (response.getString("status").equals("success")) {
                return true;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    public boolean sendDraft(String mID, String to, String cc, String subject, String body) {
        try {
            JSONObject response = new JSONObject(
                    this.postData(getNameValues(mID, to, cc, subject, body), "message", "update", "send"));

            if (response.getString("status").equals("success")) {
                if (response.getString("message").contains("Sent:")) {
                    return true;
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    public String forwardMessage(String mId) {
        try {
            ArrayList list = new ArrayList<NameValuePair>();
            list.add(new BasicNameValuePair("mid", mId));
            JSONObject result = new JSONObject(this.postData(list, "message", "forward"));
            return result.getJSONObject("result").getJSONArray("created").getString(0);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    public String replyMessage(boolean toAll, String mId) {
        try {
            ArrayList list = new ArrayList<NameValuePair>();
            list.add(new BasicNameValuePair("mid", mId));
            if (toAll) {
                list.add(new BasicNameValuePair("reply_all", "reply to all"));
            }
            JSONObject result = new JSONObject(this.postData(list, "message", "reply"));
            return result.getJSONObject("result").getJSONArray("created").getString(0);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    public String getAttachmentUrl(String messageId, String attachmentId) {
        String url = this.baseUrl;
        url += "/message/download/=" + messageId;
        url += "/part:" + attachmentId;
        url += "/";
        return url;
    }
}