yet.another.hackernews.reader.functions.DownloadText.java Source code

Java tutorial

Introduction

Here is the source code for yet.another.hackernews.reader.functions.DownloadText.java

Source

/*******************************************************************************
 * Copyright 2012 Niklas Ekman
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 *******************************************************************************/
package yet.another.hackernews.reader.functions;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Calendar;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Handler;

/**
 * Downloads text from an internet site. This is perfect to use in conjuction
 * with a server that echoes JSON arrays. Class features listener, cache and
 * progress updates (if server sends content-length header).<br>
 * <br>
 * An id can be attached to each instance to identify wich DownloadText has
 * finished.<br>
 * <br>
 * If class gives you an error you can identify the error by using a switch and
 * the public error constants.
 * 
 * 
 * @author Niklas Ekman
 * @since 11 FEB 2012
 * 
 */
public class DownloadText extends AsyncTask<String, Integer, String> {

    public static final String CACHE = "CACHE";
    public static final String CACHE_LAST_DATE = "DATE";
    public static final String CACHE_LAST_TIME = "TIME";

    /**
     * Called when download text is finished. With functions onFinished,
     * onError, onCancel and onProgressUpdate.
     * 
     * @author Niklas Ekman
     * 
     */
    public interface OnDownloadTextListener {
        /**
         * Called when download text has finished.
         * 
         * @param text
         *            - Results from download
         */
        public void onFinished(int id, String text);

        /**
         * Called if an error occurs.
         */
        public void onError(int error);

        /**
         * Called when download text has ben cancelled.
         */
        public void onCancel();
    }

    // Error constants
    public static final int MALFORMED_URL_ERROR = -1;
    public static final int IOEXCEPTION_ERROR = -2;

    // Charset used for stream
    private Charset charset;
    // Listener for results
    private OnDownloadTextListener listener;
    // ID of instance
    private int id;
    // Buffer size for stream
    private int bufferSize;

    private boolean useCache;
    private final Handler handler;
    private final Context context;
    private boolean forceUpdate;

    /**
     * DownloadImage constructor.
     * 
     * @param useCache
     *            - Do you want to soft cache results?
     */
    public DownloadText(Context context) {
        this.handler = new Handler();
        this.context = context;

        // Defaults
        charset = Charset.forName("UTF-8");
        id = 0;
        bufferSize = 8192;
        useCache = true;
        forceUpdate = false;
    }

    /**
     * Force update dispite cache
     * 
     * @param force
     */
    public void forceUpdate(boolean force) {
        this.forceUpdate = force;
    }

    public void setUseCache(boolean useCache) {
        this.useCache = useCache;
    }

    /**
     * Attach an interface to be able to track statuses.
     * 
     * @param listener
     */
    public void setOnDownloadTextListener(OnDownloadTextListener listener) {
        this.listener = listener;
    }

    /**
     * Set charset used for stream. Charset defaults UTF8.
     * 
     * @param charset
     *            charset to use for connection stream.
     * @return returns false if UnsupportedCharsetException is catched. If
     *         false, default will be used.
     */
    public boolean setCharset(String charset) {
        try {
            this.charset = Charset.forName(charset);
        } catch (UnsupportedCharsetException e) {
            this.charset = Charset.forName("UTF-8");
            return false;
        }

        return true;
    }

    /**
     * Sets buffered readers buffer size. Good to estimate and set. Defaults
     * 8192.
     * 
     * @param bufferSize
     */
    public void setBufferSize(int bufferSize) {
        this.bufferSize = bufferSize;
    }

    @Override
    protected String doInBackground(String... urlAdress) {
        /*
         * Core of the class that does the background task.
         */

        id = urlAdress[0].hashCode();

        // If we want to use the cache, try and get it first
        String cacheResults = null;
        if (useCache) {
            cacheResults = HardCache.getString(id, context);

            if (cacheResults != null) {
                if (!forceUpdate && !Cache.doUpdate(context, id)) {
                    return cacheResults;
                }
            }
        }

        // Set target url
        URL url;
        try {
            url = new URL(urlAdress[0]);
        } catch (MalformedURLException e) {
            // If listener is attached, report error.
            if (listener != null) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        listener.onError(MALFORMED_URL_ERROR);

                    }

                });
            }

            return cacheResults;
        }

        // Stream to URL adress
        BufferedReader bufferedReader = null;
        LineIterator line = null;

        // Results, size set to 100 chars
        final StringBuffer textResults = new StringBuffer(0);

        try {
            // Open stream
            bufferedReader = new BufferedReader(new InputStreamReader(url.openStream(), charset), bufferSize);

            // Download text
            line = IOUtils.lineIterator(bufferedReader);
            while (line.hasNext()) {
                if (this.isCancelled()) {
                    break;
                }

                textResults.append(line.nextLine());
            }

        } catch (IOException e) {
            if (textResults.length() > 0) {
                textResults.delete(0, textResults.length());
            }

            if (cacheResults != null) {
                textResults.append(cacheResults);
            }

            if (listener != null) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        listener.onError(IOEXCEPTION_ERROR);
                    }

                });
            }
        } finally {
            // Close reader
            LineIterator.closeQuietly(line);
            IOUtils.closeQuietly(bufferedReader);
        }

        if (textResults.length() <= 0) {
            return null;
        }

        // If use cache, put it in cache
        if (useCache && (!textResults.toString().equals(cacheResults))) {

            HardCache.putString(id, textResults.toString(), context);
            Cache.saveUpdate(context, id);

        }

        return textResults.toString();
    }

    @Override
    protected void onPostExecute(String result) {
        /*
         * Called when background task is finished. Gives a result as argument
         */
        super.onPostExecute(result);

        // If we have a listener attached and no error occured, report string.
        if (listener != null) {
            listener.onFinished(id, result);
        }
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
        /*
         * Called if class is canceled.
         */

        // If we have a listener attached, report cancel.
        if (listener != null) {
            listener.onCancel();
        }
    }

    private static class HardCache {

        public static void putString(int key, String text, Context context) {
            FileOutputStream fos = null;

            try {
                fos = context.openFileOutput("" + key, Context.MODE_PRIVATE);

                fos.write(text.getBytes());
                fos.flush();
                fos.close();
            } catch (FileNotFoundException e) {
            } catch (IOException e) {
            } finally {
                try {
                    if (fos != null) {
                        fos.close();
                    }
                } catch (IOException e1) {
                }
            }
        }

        public static String getString(int key, Context context) {
            if (!context.getFileStreamPath("" + key).exists()) {
                return null;
            }

            FileInputStream fis = null;
            ByteBuffer buf = null;

            try {
                fis = context.openFileInput("" + key);
                buf = ByteBuffer.allocate(fis.available());
                fis.read(buf.array());
            } catch (FileNotFoundException e) {
            } catch (IOException e) {
            } finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                    }
                }
            }

            if (buf == null) {
                return null;
            }

            return new String(buf.array());
        }
    }

    private static class Cache {

        private static final long INTERVAL = 15 * 60 * 1000;

        private static long getTime() {
            Calendar cal = Calendar.getInstance();
            return cal.getTimeInMillis();
        }

        private static int getDate() {
            Calendar cal = Calendar.getInstance();
            final int year = cal.get(Calendar.YEAR) * 10000;
            final int month = cal.get(Calendar.MONTH) * 100;
            final int day = cal.get(Calendar.DAY_OF_MONTH);
            return year + month + day;
        }

        public static boolean doUpdate(Context context, int key) {
            SharedPreferences prefs = context.getSharedPreferences(CACHE, Context.MODE_PRIVATE);

            final int lastUpdateDate = prefs.getInt(CACHE_LAST_DATE + key, getDate());
            final long lastUpdateTime = prefs.getLong(CACHE_LAST_TIME + key, getTime());

            if (getDate() > lastUpdateDate || (getTime() - INTERVAL) > lastUpdateTime) {
                return true;
            }

            return false;
        }

        public static void saveUpdate(Context context, int key) {
            SharedPreferences prefs = context.getSharedPreferences(CACHE, Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = prefs.edit();
            editor.putInt(CACHE_LAST_DATE + key, getDate());
            editor.putLong(CACHE_LAST_TIME + key, getTime());
            editor.commit();
        }
    }
}