com.android.flickrbot.FlickrClient.java Source code

Java tutorial

Introduction

Here is the source code for com.android.flickrbot.FlickrClient.java

Source

/**
 *  FlickrBot: A Flickr upload client for Android
 *  Copyright (C) 2009 Matt Mets
 *
 *  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.android.flickrbot;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Iterator;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Simple client library to access the Flickr API from Android
 * 
 * @author Matt Mets
 */
public class FlickrClient {

    // Note: Don't screw with these.  If you intend to fork this application or
    //       do something else with this code, get your own API key from Flickr.
    //       It's free and painless.
    private static final String KEY = "ae428503428bda12221b5d170edca5a0";
    private static final String SECRET = "b0d05b2be4e7cee4";

    private static final String FROB_NAME = "frob";
    private static final String AUTH_TOKEN_NAME = "auth_token";
    private static final String NSID_NAME = "nsid";
    private static final String USERNAME_NAME = "username";

    private static final String REST_URL = "http://api.flickr.com/services/rest/";
    private static final String UPLOAD_URL = "http://api.flickr.com/services/upload/";
    private static final String AUTH_URL = "http://m.flickr.com/services/auth/";

    /**
     * Representation of a list of parameters that will be passed to the
     * Flickr API in a HttpPost request.  Includes a facility to sign the
     * parameter list while retrieving it. 
     * TODO: Make this an extension of the multipart object to skip a step.
     */
    public class parameterList {
        private TreeMap<String, String> parameters = new TreeMap<String, String>();

        public void addParameter(String name, String value) {
            parameters.put(name, value);
        }

        public void clear() {
            parameters.clear();
        }

        /**
         * Get a string representation of the parameter list, suitable for a
         * GET query.  Note that this automatically signs the parameter list.
         * */
        public String getSignedList() throws Exception {
            // Add each of the parameters in the form ?keya=valuea&keyb=valueb
            String list = "?";

            Iterator iterator = parameters.keySet().iterator();
            boolean firstItem = true;
            while (iterator.hasNext()) {
                if (firstItem) {
                    firstItem = false;
                } else {
                    list += "&";
                }

                Object key = iterator.next();
                list += key + "=" + parameters.get(key);
            }

            list += "&api_sig=" + getSig();

            return list;
        }

        /**
         * Get a MultipartEntity object representation of the parameter list,
         * suitable for passing to a httpClient() execute.
         */
        public MultipartEntity getMultipartEntity() throws Exception {
            MultipartEntity entity = new MultipartEntity();

            Iterator<String> iterator = parameters.keySet().iterator();
            while (iterator.hasNext()) {
                Object key = iterator.next();
                try {
                    entity.addPart(key.toString(), new StringBody(parameters.get(key)));
                } catch (UnsupportedEncodingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            entity.addPart("api_sig", new StringBody(getSig()));

            return entity;
        }

        /** Get the MD5 signature of the parameter list */
        public String getSig() throws Exception {
            /** Build a sorted list of all the parameters, prepended by the
             *  secret. */
            String list = SECRET;
            Iterator<String> iterator = parameters.keySet().iterator();
            while (iterator.hasNext()) {
                Object key = iterator.next();
                list += key + parameters.get(key);
            }

            /** Calculate the MD5 hash of the parameter string */
            return MD5(list);
        }
    }

    Database m_database;

    /**
     * FlickrHelper constructor
     * @param database Parameter database to use
     */
    public FlickrClient(Database database) {
        m_database = database;
    }

    public enum AuthorizationState {
        NOT_STARTED, WAITING_FOR_AUTHORIZATION, AUTHORIZED, NO_LONGER_VALID
    }

    /**
     * Determine what stage of the authorization state that the user is in.
     * @return The current authorization state
     * @throws Exception Throws an exception if there is a communication error.
     */
    AuthorizationState getAuthorizationState() throws Exception {

        // First, attempt to retrieve the authentication token.
        String auth_token = m_database.getConfigValue("auth_token");

        // If auth_token has a value, assume that the user has completed
        // registration and return.  
        if (auth_token != null) {
            // TODO: Verify that the authorization is current by polling Flickr.
            return AuthorizationState.AUTHORIZED;
        }

        // Otherwise, the user hasn't completed registration.  Determine if
        // there is an ongoing registration that should be continued.

        // A registration is in progress if a frob exists in the database.
        String frob = m_database.getConfigValue("frob");
        if (frob == null) {
            return AuthorizationState.NOT_STARTED;
        }

        // Determine if the registration in progress has been completed by
        // the user.  Note: AuthToken will throw if communication fails with
        // Flickr for any reason.
        if (getAuthToken()) {
            return AuthorizationState.AUTHORIZED;
        } else {
            return AuthorizationState.WAITING_FOR_AUTHORIZATION;
        }

    }

    /**
     * Upload a photo to Flickr using the current user credentials.
     * 
     * @param title Photo title
     * @param description Photo description
     * @param tags Tags to assign to photo
     * @param image JPEG object representing the photo
     */
    public void sendPhoto(String title, String description, String tags, InputStream image) throws Exception {
        // TODO: Verify that we are authorized first.

        // Build a representation of the parameter list, and use it to sign the request.
        parameterList params = this.new parameterList();
        params.addParameter("api_key", KEY);
        params.addParameter("auth_token", m_database.getConfigValue(AUTH_TOKEN_NAME));

        // Add all of the extra parameters that are passed along with the image.
        if (title.length() > 0) {
            params.addParameter("title", title);
        }

        if (description.length() > 0) {
            params.addParameter("description", description);
        }

        if (tags.length() > 0) {
            params.addParameter("tags", tags);
        }

        // Build a multipart HTTP request to post to Flickr

        // Add the text parameters
        MultipartEntity multipart = params.getMultipartEntity();

        // then the photo data
        multipart.addPart("photo", new InputStreamBody(image, "photo"));

        // Build the rest of the players
        HttpClient client = new DefaultHttpClient(); // HttpClient makes requests
        HttpPost postrequest = new HttpPost(UPLOAD_URL); // HttpPost is the type of request we are making
        postrequest.setEntity(multipart);
        HttpResponse response; // HttpResponse holds the return data from HttpPost 

        // TODO: Is this necessary?
        if (postrequest == null) {
            // Building the post request has failed
            throw new Exception("postrequest could not be built");
        }

        // Post the photo.  This will throw an exception if the connection fails.
        response = client.execute(postrequest);

        System.out.println("POST response code: " + response.getStatusLine().getReasonPhrase());
        HttpEntity resp = response.getEntity();

        if (resp == null) {
            throw new Exception("Response entity is empty.");
        }

        byte[] b = new byte[999];
        resp.getContent().read(b);
        System.out.println("Response: " + new String(b));

    }

    /**
     * Retrieves a URL that the user should browse to in order to authorize
     * their account with the program.  This stores a value in the class
     * variable frob, which is used in getAuthToken() later to complete the
     * authentication.
     * 
     * @return URL that the program should redirect the user to, in order to complete authorization. 
     */
    public String startAuthorization() throws Exception {
        m_database.deleteConfigValue(FROB_NAME);
        m_database.deleteConfigValue(AUTH_TOKEN_NAME);

        /**
         * Request a frob to identify the login session. This call requires 
         * a signature. The signature starts with your shared secret and
         * is followed by your API key and the method name. The API key and
         * method name are prepended by the words "api_key" and "method" as
         * shown in the following line.
        **/
        String methodGetFrob = "flickr.auth.getFrob";

        parameterList params = this.new parameterList();
        params.addParameter("api_key", KEY);
        params.addParameter("method", methodGetFrob);

        HttpClient client = new DefaultHttpClient();
        HttpGet getrequest = new HttpGet(REST_URL + params.getSignedList());

        // Send GET request
        HttpResponse response = client.execute(getrequest);
        int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode != HttpStatus.SC_OK) {
            throw new Exception("Method failed: " + response.getStatusLine());
        }
        InputStream rstream = null;

        // Get the response body
        rstream = response.getEntity().getContent();

        /**
         * Retrieve the XML response to the frob request and get the frob value.
         */
        Document parsedResponse = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(rstream);

        String frob;

        // Check if frob is in the response
        NodeList frobResponse = parsedResponse.getElementsByTagName("frob");
        Node frobNode = frobResponse.item(0);
        if (frobNode != null) {
            frob = frobNode.getFirstChild().getNodeValue();
            System.out.println("Successfully retrieved frob: " + frob);
            m_database.setConfigValue(FROB_NAME, frob);
        } else {
            // Get Flickr error code and msg
            NodeList error = parsedResponse.getElementsByTagName("err");
            String code = error.item(0).getAttributes().item(0).getNodeValue();
            String msg = error.item(0).getAttributes().item(1).getNodeValue();

            throw new Exception("Flickr request failed with error code: " + code + ", " + msg);
        }

        /**
         * Create a Flickr login link
         * http://www.flickr.com/services/auth/?api_key=[api_key]&perms=[perms]&frob=[frob]&api_sig=[api_sig] 
         */
        params.clear();
        params.addParameter("api_key", KEY);
        params.addParameter("frob", frob);
        params.addParameter("perms", "write");

        return AUTH_URL + params.getSignedList();
    }

    boolean getAuthToken() throws Exception {
        // TODO: Check the state before running this.

        /** Retrieve the frob from the database **/
        String frob = m_database.getConfigValue(FROB_NAME);

        /**
         * Get auth token using frob. Once again, a signature is required
         * for authenticated calls to the Flickr API.  
         */
        parameterList params = this.new parameterList();
        params.addParameter("api_key", KEY);
        params.addParameter("frob", frob);
        params.addParameter("method", "flickr.auth.getToken");

        HttpClient client = new DefaultHttpClient();
        HttpGet getrequest = new HttpGet(REST_URL + params.getSignedList());

        // Send GET request
        HttpResponse response = client.execute(getrequest);
        int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode != HttpStatus.SC_OK) {
            throw new Exception("Method failed: " + response.getStatusLine());
        }

        InputStream rstream = null;

        // Get the response body
        rstream = response.getEntity().getContent();
        /**
         * Retrieve the XML response to the token request and get the token value
         */
        Document parsedResponse = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(rstream);

        String token = null;

        // Check if token is in the response
        NodeList tokenResponse = parsedResponse.getElementsByTagName("token");
        Node tokenNode = tokenResponse.item(0);

        if (tokenNode == null) {
            NodeList error = parsedResponse.getElementsByTagName("err");
            // Get Flickr error code and msg
            // TODO: Define this better
            String code = error.item(0).getAttributes().item(0).getNodeValue();
            String msg = error.item(0).getAttributes().item(1).getNodeValue();
            System.out.println("Flickr request failed with error code " + code + ", " + msg);

            return false;
        }

        token = tokenNode.getFirstChild().getNodeValue();
        System.out.println("Successfully retrieved token: " + token);

        // TODO: Save other stuff such as the user name, etc?
        m_database.deleteConfigValue(FROB_NAME);
        m_database.setConfigValue(AUTH_TOKEN_NAME, token);

        // TODO: Check that the perms field is "write"

        // Assume that all of this will work.  If they don't, we throw.
        tokenResponse = parsedResponse.getElementsByTagName("user");
        String nsid = tokenResponse.item(0).getAttributes().getNamedItem("nsid").getNodeValue();
        String username = tokenResponse.item(0).getAttributes().getNamedItem("username").getNodeValue();

        m_database.setConfigValue(NSID_NAME, nsid);
        m_database.setConfigValue(USERNAME_NAME, username);

        return true;
    }

    /**
     * Get the MD5 hash of a text string
     */
    public static String MD5(String text) throws Exception {
        String md5Text = "";

        MessageDigest digest = MessageDigest.getInstance("MD5");
        md5Text = new BigInteger(1, digest.digest((text).getBytes())).toString(16);

        if (md5Text.length() == 31) {
            md5Text = "0" + md5Text;
        }
        return md5Text;
    }
}