DeliciousApiRequest.java :  » UnTagged » android-delicious-bookmarks » org » peterbaldwin » client » android » delicious » Android Open Source

Android Open Source » UnTagged » android delicious bookmarks 
android delicious bookmarks » org » peterbaldwin » client » android » delicious » DeliciousApiRequest.java
/*-
 *  Copyright (C) 2009 Peter Baldwin   
 *  
 *  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 org.peterbaldwin.client.android.delicious;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;

// TODO: improve error messages
public class DeliciousApiRequest implements Runnable {
  @SuppressWarnings("serial")
  private static class ConnectionException extends IOException {
    private final int mStatusCode;

    private ConnectionException(int statusCode) {
      mStatusCode = statusCode;
    }

    public int getStatusCode() {
      return mStatusCode;
    }
  }

  private static final String LOG_TAG = "DeliciousApiRequest";
  // TODO: make error codes into constructor parameters?
  public static final int HANDLE_AUTH_ERROR = -2;
  public static final int HANDLE_ERROR = -1;
  public static final int HANDLE_DONE = 0;

  public static final int REQUEST_POSTS_ADD = 1;
  public static final int REQUEST_POSTS_SUGGEST = 2;
  public static final int REQUEST_TAGS_GET = 3;

  private static final int BUFFER_SIZE = 1024;

  private final int mRequestType;
  private final int mRequestId;
  private final Uri mUri;
  private final HttpClient mClient;
  private final DeliciousApiResponseHandler mResponseHandler;
  private final Handler mHandler;
  private File mCacheFile;
  private boolean mUseCache;
  private boolean mUpdateCache;
  private boolean mAlwaysUpdateCache;

  DeliciousApiRequest(int requestType, int requestId, Uri uri,
      HttpClient client, DeliciousApiResponseHandler responseHandler,
      Handler handler) {
    super();
    mRequestType = requestType;
    mRequestId = requestId;
    mUri = uri;
    mClient = client;
    mResponseHandler = responseHandler;
    mHandler = handler;
  }

  private void setError(Message msg, Throwable t) {
    if (mCacheFile != null) {
      mCacheFile.delete();
    }
    String error = String.valueOf(t);
    msg.what = HANDLE_ERROR;
    msg.obj = error;
    Log.e(LOG_TAG, error, t);
  }

  private InputStream readFromCache() throws IOException {
    long start = now();
    try {
      if (mCacheFile == null) {
        throw new NullPointerException();
      }
      if (mCacheFile.exists()) {
        InputStream in = new FileInputStream(mCacheFile);
        in = new BufferedInputStream(in);
        return in;
      }
      return null;
    } finally {
      logTiming("read cache", start);
    }
  }

  private InputStream updateCache(InputStream in) throws IOException {
    long start = now();
    try {
      try {
        if (mCacheFile == null) {
          throw new NullPointerException();
        }
        File parent = mCacheFile.getParentFile();

        // An exception will be thrown below when trying to write
        // the file if the parent directories were not created.
        parent.mkdirs();

        mCacheFile.createNewFile();
        OutputStream fout = new FileOutputStream(mCacheFile);
        fout = new BufferedOutputStream(fout);
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        try {
          byte[] buffer = new byte[BUFFER_SIZE];
          while (true) {
            int size = in.read(buffer);
            if (size < 0) {
              break;
            }
            bout.write(buffer, 0, size);
            fout.write(buffer, 0, size);
          }
        } finally {
          bout.close();
          fout.close();
        }
        byte[] data = bout.toByteArray();
        return new ByteArrayInputStream(data);
      } finally {
        in.close();
      }
    } finally {
      logTiming("update cache", start);
    }
  }

  private InputStream readFromNetwork() throws IOException {
    long start = now();
    try {
      HttpGet request = new HttpGet(String.valueOf(mUri));
      HttpResponse response = mClient.execute(request);
      StatusLine status = response.getStatusLine();
      int statusCode = status.getStatusCode();
      if (statusCode != HttpStatus.SC_OK) {
        throw new ConnectionException(statusCode);
      }
      HttpEntity entity = response.getEntity();
      InputStream in = entity.getContent();
      in = new BufferedInputStream(in);
      return in;
    } finally {
      logTiming("read network", start);
    }
  }

  private static long now() {
    return SystemClock.uptimeMillis();
  }

  private static void logTiming(String action, long start) {
    long end = now();
    long duration = end - start;
    String msg = String.format("%s took %d ms", action, duration);
    Log.d(LOG_TAG, msg);
  }

  /**
   * {@inheritDoc}
   */
  public void run() {
    Message msg = mHandler.obtainMessage();
    msg.arg1 = mRequestType;
    msg.arg2 = mRequestId;
    msg.what = HANDLE_ERROR;
    msg.obj = "unknown error";
    boolean cacheUpdated = false;
    try {
      SAXParserFactory factory = SAXParserFactory.newInstance();
      SAXParser parser = factory.newSAXParser();
      XMLReader reader = parser.getXMLReader();

      ContentHandler handler = mResponseHandler.getContentHandler();
      reader.setContentHandler(handler);

      InputStream in = null;
      if (in == null && mUseCache && mCacheFile != null) {
        in = readFromCache();
      }
      if (in == null) {
        in = readFromNetwork();

        if (mUpdateCache && mCacheFile != null) {
          in = updateCache(in);
          cacheUpdated = true;
        }
      }
      try {
        InputSource input = new InputSource(in);
        long start = now();
        try {
          reader.parse(input);
        } finally {
          logTiming("parse", start);
        }
      } finally {
        in.close();
      }
      msg.what = HANDLE_DONE;
      msg.obj = mResponseHandler;
    } catch (ConnectionException e) {
      setError(msg, e);
      int statusCode = e.getStatusCode();
      if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
        msg.what = HANDLE_AUTH_ERROR;
        msg.obj = "invalid username or password";
      } else {
        msg.what = HANDLE_ERROR;
        msg.obj = "unexpected response: " + statusCode;
      }
    } catch (IOException e) {
      setError(msg, e);
    } catch (ParserConfigurationException e) {
      setError(msg, e);
    } catch (SAXException e) {
      setError(msg, e);
    } catch (RuntimeException e) {
      setError(msg, e);
    } catch (Error e) {
      setError(msg, e);
    } finally {
      mHandler.sendMessage(msg);
    }

    if (msg.what != HANDLE_AUTH_ERROR && !cacheUpdated
        && mAlwaysUpdateCache && mCacheFile != null) {
      // If the authentication is valid, but the cache was not updated,
      // silently update the cache in the background after dispatching the
      // result to the handler.
      try {
        InputStream in = readFromNetwork();
        in = updateCache(in);
        in.close();
        Log.i(LOG_TAG, "cache file updated: " + mCacheFile);
      } catch (IOException e) {
        Log.e(LOG_TAG, "error updating cache", e);
      } catch (RuntimeException e) {
        Log.e(LOG_TAG, "error updating cache", e);
      } catch (Error e) {
        Log.e(LOG_TAG, "error updating cache", e);
      }
    }
  }

  public void setCacheFile(File cacheFile) {
    mCacheFile = cacheFile;
  }

  public void setUseCache(boolean useCache) {
    mUseCache = useCache;
  }

  public void setUpdateCache(boolean updateCache) {
    mUpdateCache = updateCache;
  }

  public void setAlwaysUpdateCache(boolean alwaysUpdateCache) {
    mAlwaysUpdateCache = alwaysUpdateCache;
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.