Java tutorial
/* * 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", " "); } 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; } }