Java tutorial
/* * Copyright (C) 2005-2010 Team XBMC * http://xbmc.org * * 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 2, 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 XBMC Remote; see the file license. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ package org.xbmc.httpapi; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import org.apache.http.HttpException; import org.xbmc.android.util.Base64; import org.xbmc.android.util.ClientFactory; import org.xbmc.api.business.INotifiableManager; import org.xbmc.api.object.Host; import android.util.Log; /** * Singleton class. Will be instantiated only once * * @author Team XBMC */ public class Connection { private static final String TAG = "Connection"; private static final String XBMC_HTTP_BOOTSTRAP = "/xbmcCmds/xbmcHttp"; private static final String XBMC_MICROHTTPD_THUMB_BOOTSTRAP = "/thumb/"; private static final String XBMC_MICROHTTPD_VFS_BOOTSTRAP = "/vfs/"; private static final int SOCKET_CONNECTION_TIMEOUT = 5000; /** * Singleton class instance */ private static Connection sConnection; /** * Complete URL without any attached command parameters, for instance: * <code>http://192.168.0.10:8080</code> */ private String mUrlSuffix; /** * Socket read timeout (connection timeout is default) */ private int mSocketReadTimeout = 0; /** * Holds the base64 encoded user/pass for http authentication */ private String authEncoded = null; /** * Use getInstance() for public class instantiation * @param host XBMC host * @param port HTTP API port */ private Connection(String host, int port) { setHost(host, port); } /** * Returns the singleton instance of this connection. Note that host and * port settings are only looked at the first time. Use {@link setHost()} * if you want to update these parameters. * @param host XBMC host * @param port HTTP API port * @return Connection instance */ public static Connection getInstance(String host, int port) { if (sConnection == null) { sConnection = new Connection(host, port); } if (sConnection.mUrlSuffix == null) { sConnection.setHost(host, port); } return sConnection; } /** * Updates host info of the connection instance * @param host */ public void setHost(Host host) { if (host == null) { setHost(null, 0); } else { setHost(host.addr, host.port); setAuth(host.user, host.pass); } } /** * Updates host and port parameters of the connection instance. * @param host Host or IP address of the host * @param port Port the HTTP API is listening to */ public void setHost(String host, int port) { if (host == null || port <= 0) { mUrlSuffix = null; } else { StringBuilder sb = new StringBuilder(); sb.append("http://"); sb.append(host); sb.append(":"); sb.append(port); mUrlSuffix = sb.toString(); } } /** * Sets authentication info * @param user HTTP API username * @param pass HTTP API password */ public void setAuth(String user, String pass) { if (user != null && pass != null) { String auth = user + ":" + pass; authEncoded = Base64.encodeBytes(auth.getBytes()).toString(); } else { authEncoded = null; } } /** * Sets socket read timeout (connection timeout has constant value) * @param timeout Read timeout in milliseconds. */ public void setTimeout(int timeout) { if (timeout > 0) { mSocketReadTimeout = timeout; } } /** * Returns the full URL of an HTTP API request * @param command Name of the command to execute * @param parameters Parameters, separated by ";". * @return Absolute URL to HTTP API */ public String getUrl(String command, String parameters) { // create url StringBuilder sb = new StringBuilder(mUrlSuffix); sb.append(XBMC_HTTP_BOOTSTRAP); sb.append("?command="); sb.append(command); sb.append("("); sb.append(URLEncoder.encode(parameters)); sb.append(")"); return sb.toString(); } /** * Returns an input stream pointing to a HTTP API command. * @param command Name of the command to execute * @param parameters Parameters, separated by ";". * @param manager Reference back to business layer * @return */ public InputStream getThumbInputStream(String command, String parameters, INotifiableManager manager) { URLConnection uc = null; try { if (mUrlSuffix == null) { throw new NoSettingsException(); } URL url = new URL(getUrl(command, parameters)); uc = getUrlConnection(url); Log.i(TAG, "Preparing input stream from " + url); return uc.getInputStream(); } catch (MalformedURLException e) { manager.onError(e); } catch (IOException e) { manager.onError(e); } catch (NoSettingsException e) { manager.onError(e); } return null; } /** * Returns an input stream pointing to a HTTP API command. * @param command Name of the command to execute * @param parameters Parameters, separated by ";". * @param manager Reference back to business layer * @return */ public InputStream getThumbInputStreamForMicroHTTPd(String thumb, INotifiableManager manager) throws FileNotFoundException { URLConnection uc = null; try { if (mUrlSuffix == null) { throw new NoSettingsException(); } final URL url; if (ClientFactory.XBMC_REV > 0 && ClientFactory.XBMC_REV >= ClientFactory.THUMB_TO_VFS_REV) { url = new URL(mUrlSuffix + XBMC_MICROHTTPD_VFS_BOOTSTRAP + URLEncoder.encode(thumb)); } else { url = new URL(mUrlSuffix + XBMC_MICROHTTPD_THUMB_BOOTSTRAP + thumb + ".jpg"); } Log.i(TAG, "Preparing input stream from " + url + " for microhttpd.."); uc = getUrlConnection(url); return uc.getInputStream(); } catch (FileNotFoundException e) { throw e; } catch (MalformedURLException e) { manager.onError(e); } catch (IOException e) { manager.onError(e); } catch (NoSettingsException e) { manager.onError(e); } return null; } /** * Executes a query. * @param command Name of the command to execute * @param parameters Parameters, separated by ";". * @param manager Reference back to business layer * @return HTTP response string. */ public String query(String command, String parameters, INotifiableManager manager) { URLConnection uc = null; try { if (mUrlSuffix == null) { throw new NoSettingsException(); } URL url = new URL(getUrl(command, parameters)); uc = getUrlConnection(url); final String debugUrl = URLDecoder.decode(url.toString()); Log.i(TAG, debugUrl); final BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()), 8192); final StringBuilder response = new StringBuilder(); String line; while ((line = in.readLine()) != null) { response.append(line); } in.close(); return response.toString().replace("<html>", "").replace("</html>", ""); } catch (MalformedURLException e) { manager.onError(e); } catch (IOException e) { int responseCode = -1; try { if (uc != null) { responseCode = ((HttpURLConnection) uc).getResponseCode(); } } catch (IOException e1) { } // do nothing, getResponse code failed so treat as default i/o exception. if (uc != null && responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { manager.onError(new HttpException(Integer.toString(HttpURLConnection.HTTP_UNAUTHORIZED))); } else { manager.onError(e); } } catch (NoSettingsException e) { manager.onError(e); } return ""; } /** * Executes an HTTP API method and returns the result as string. * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @return Result */ public String getString(INotifiableManager manager, String method, String parameters) { return query(method, parameters, manager).replaceAll(LINE_SEP, "").trim(); } /** * Executes an HTTP API method and returns the result as string. * @param method Name of the method to run * @return Result */ public String getString(INotifiableManager manager, String method) { return getString(manager, method, ""); } /** * Executes an HTTP API method and returns the result as integer. * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @return Result */ public int getInt(INotifiableManager manager, String method, String parameters) { try { return Integer.parseInt(getString(manager, method, parameters)); } catch (NumberFormatException e) { return 0; } } /** * Executes an HTTP API method without parameter and returns the result as * integer. * @param method Name of the method to run * @return Result */ public int getInt(INotifiableManager manager, String method) { return getInt(manager, method, ""); } /** * Executes an HTTP API method and makes sure the result is OK (or something * like that) * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @throws WrongDataFormatException If not "OK" */ public boolean assertBoolean(INotifiableManager manager, String method, String parameters) throws WrongDataFormatException { final String ret = query(method, parameters, manager); if (ret.contains("OK") || ret.contains("true") || ret.contains("True") || ret.contains("TRUE")) { return true; } else if (ret.contains("false") || ret.contains("False") || ret.contains("FALSE")) { return false; } else { throw new WrongDataFormatException("OK", ret); } } /** * Executes an HTTP API method and makes sure the result is OK (or something * like that) * @param method Name of the method to run * @throws WrongDataFormatException If not "OK" */ public boolean assertBoolean(INotifiableManager manager, String method) throws WrongDataFormatException { return assertBoolean(manager, method, ""); } /** * Executes an HTTP API method and returns the result as boolean. * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @return Result */ public boolean getBoolean(INotifiableManager manager, String method, String parameters) { try { return assertBoolean(manager, method, parameters); } catch (WrongDataFormatException e) { return false; } } /** * Executes an HTTP API method and returns the result as boolean. * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @return Result */ public boolean getBoolean(INotifiableManager manager, String method) { return getBoolean(manager, method, ""); } /** * Executes an HTTP API method and returns the result in a list of strings. * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" */ public ArrayList<String> getArray(INotifiableManager manager, String method, String parameters) { final String[] rows = query(method, parameters, manager).split(LINE_SEP); final ArrayList<String> result = new ArrayList<String>(); for (String row : rows) { if (row.length() > 0) { result.add(row.trim()); } } return result; } /** * Executes an HTTP API method and returns the result as a list of * key => value pairs * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @return */ public HashMap<String, String> getPairs(INotifiableManager manager, String method, String parameters) { final String[] rows = query(method, parameters, manager).split(LINE_SEP); final HashMap<String, String> result = new HashMap<String, String>(); for (String row : rows) { final String[] pair = row.split(PAIR_SEP, 2); if (pair.length == 1) { result.put(pair[0].trim(), ""); } else if (pair.length == 2 && pair[0].trim().length() > 0) { result.put(pair[0].trim(), pair[1].trim()); } } return result; } /** * Executes an HTTP API method without parameter and returns the result as * a list of key => value pairs * @param method Name of the method to run * @return */ public HashMap<String, String> getPairs(INotifiableManager manager, String method) { return getPairs(manager, method, ""); } /** * Create a new URLConnection with the request headers set, including authentication. * * @param url The request url * @return URLConnection * @throws IOException */ private URLConnection getUrlConnection(URL url) throws IOException { final URLConnection uc = url.openConnection(); uc.setConnectTimeout(SOCKET_CONNECTION_TIMEOUT); uc.setReadTimeout(mSocketReadTimeout); uc.setRequestProperty("Connection", "close"); if (authEncoded != null) { uc.setRequestProperty("Authorization", "Basic " + authEncoded); } return uc; } public byte[] download(String pathToDownload) throws IOException, URISyntaxException { try { final URL url = new URL(pathToDownload); final URLConnection uc = getUrlConnection(url); final InputStream is = uc.getInputStream(); final InputStreamReader isr = new InputStreamReader(is); final BufferedReader rd = new BufferedReader(isr, 8192); final StringBuilder sb = new StringBuilder(); String line = ""; while ((line = rd.readLine()) != null) { sb.append(line); } rd.close(); return Base64.decode(sb.toString().replace("<html>", "").replace("</html>", "")); } catch (Exception e) { return null; } } /** * Removes the trailing "</field>" string from the value * @param value * @return Trimmed value */ public static String trim(String value) { return new String( value.replace("</record>", "").replace("<record>", "").replace("</field>", "").toCharArray()); } /** * Removes the trailing "</field>" string from the value and tries to * parse an integer from it. On error, returns -1. * @param value * @return Parsed integer from field value */ public static int trimInt(String value) { String trimmed = trim(value); if (trimmed.length() > 0) { try { return Integer.parseInt(trimmed.replace(",", "")); } catch (NumberFormatException e) { return -1; } } else { return -1; } } /** * Removes the trailing "</field>" string from the value and tries to * parse a double from it. On error, returns -1.0. * @param value * @return Parsed double from field value */ public static double trimDouble(String value) { String trimmed = trim(value); if (trimmed.length() > 0) { try { return Double.parseDouble(trimmed); } catch (NumberFormatException e) { return -1.0; } } else { return -1.0; } } /** * Removes the trailing "</field>" string from the value and tries to * parse a boolean from it. * @param value * @return Parsed boolean from field value */ public static boolean trimBoolean(String value) { String trimmed = trim(value); if (trimmed.length() > 0) { if (trimmed.startsWith("0") || trimmed.toLowerCase().startsWith("false")) { return false; } if (trimmed.startsWith("1") || trimmed.toLowerCase().startsWith("true")) { return true; } } return false; } public static final String LINE_SEP = "<li>"; public static final String VALUE_SEP = ";"; public static final String PAIR_SEP = ":"; }