CB_Utils.Util.Downloader.java Source code

Java tutorial

Introduction

Here is the source code for CB_Utils.Util.Downloader.java

Source

/* 
 * Copyright (C) 2013-2015 team-cachebox.de
 *
 * Licensed under the : GNU General Public License (GPL);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.gnu.org/licenses/gpl.html
 *
 * 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 CB_Utils.Util;

/**
 * Downloader.java
 * Copyright 2008 Zach Scrivena
 * zachscrivena@gmail.com
 * http://zs.freeshell.org/
 *
 * TERMS AND CONDITIONS:
 * 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/>.
 */

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import CB_Utils.fileProvider.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import CB_Utils.http.HttpUtils;

/**
 * Download a remote resource.
 */
public class Downloader implements Runnable {

    /** buffer size in number of bytes (1024) */
    private static final int BUFFER_SIZE = 1024;

    /** URL of the remote resource to be downloaded */
    private final URL url;

    /** target object to be populated */
    private final Object target;

    /** length of the remote resource, in number of bytes; -1 if unknown */
    private int totalLength = -1;

    /** number of bytes downloaded */
    private int downloadedLength = 0;

    /** mutex lock for fields totalLength and downloadedLength */
    private final Object lengthLock = new Object();

    /** string describing the current progress */
    volatile private String progressString = "Waiting to start";

    /** has there been an update in the progress? */
    volatile private boolean progressUpdated = false;

    /** Exception object representing the error, if any */
    volatile private Exception error = null;

    /** has the download started? */
    private boolean started = false;

    /** is the downloader running? */
    private boolean running = false;

    /** is the download cancelled? */
    private boolean cancelled = false;

    /** is the download completed? */
    private boolean completed = false;

    /** mutex lock for fields started, running, cancelled, and completed */
    private final Object stateLock = new Object();

    /**
     * Constructor. The target object should not be accessed until after calling waitUntilCompleted().
     * 
     * @param url
     *            URL of the remote resource to be downloaded
     * @param target
     *            target object to be populated (File or StringBuilder object)
     */
    public Downloader(final URL url, final Object target) {
        if ((target instanceof File) || (target instanceof StringBuilder)) {
            this.target = target;
        } else {
            throw new IllegalArgumentException("Target must be a File or StringBuilder object.");
        }

        this.url = url;

        synchronized (stateLock) {
            started = false;
            running = false;
        }
    }

    /**
     * Get the length of the remote resource.
     * 
     * @return length of the remote resource, in number of bytes; -1 if unknown
     */
    public int getLength() {
        synchronized (lengthLock) {
            return totalLength;
        }
    }

    /**
     * Get the number of bytes downloaded.
     * 
     * @return number of bytes downloaded
     */
    public int getDownloadedLength() {
        synchronized (lengthLock) {
            return downloadedLength;
        }
    }

    /**
     * Get a string describing the current progress.
     * 
     * @return string describing the current progress
     */
    public String getProgressString() {
        return progressString;
    }

    /**
     * Get the percentage describing the current progress.
     * 
     * @return percentage describing the current progress; -1 if unknown
     */
    public int getProgressPercent() {
        synchronized (lengthLock) {
            if ((totalLength <= 0) || (downloadedLength > totalLength)) {
                return -1;
            } else if (downloadedLength == totalLength) {
                return 100;
            } else {
                return (int) (100.0 * downloadedLength / totalLength);
            }
        }
    }

    /**
     * Has there been an update in the progress? The progressUpdated flag is set to false before this method returns.
     * 
     * @return true if there has been an update in the progress; false otherwise
     */
    public boolean isProgressUpdated() {
        if (progressUpdated) {
            progressUpdated = false;
            return true;
        } else {
            return false;
        }
    }

    /**
     * Pause the download.
     */
    public void pause() {
        synchronized (stateLock) {
            running = false;
        }
    }

    /**
     * Resume the download.
     */
    public void resume() {
        synchronized (stateLock) {
            if (!completed) {
                running = true;
            }
        }
    }

    /**
     * Cancel the download.
     */
    public void cancel() {
        synchronized (stateLock) {
            cancelled = true;
        }
    }

    /**
     * Has the download started?
     * 
     * @return true if download has started; false otherwise
     */
    public boolean isStarted() {
        synchronized (stateLock) {
            return started;
        }
    }

    /**
     * Is the downloader running?
     * 
     * @return true if downloader is running; false otherwise
     */
    public boolean isRunning() {
        synchronized (stateLock) {
            return running;
        }
    }

    /**
     * Is the download cancelled?
     * 
     * @return true if downloader is cancelled; false otherwise
     */
    public boolean isCancelled() {
        synchronized (stateLock) {
            return cancelled;
        }
    }

    /**
     * Is the download completed?
     * 
     * @return true if download is completed; false otherwise
     */
    public boolean isCompleted() {
        synchronized (stateLock) {
            return completed;
        }
    }

    /**
     * Wait until the download is completed. The target object should be accessed only after calling this method.
     * 
     * @throws Exception
     *             if an error has occurred during download, or if the download was cancelled
     */
    public void waitUntilCompleted() throws Exception {
        while (true) {
            synchronized (stateLock) {
                if (completed) {
                    if (error == null) {
                        return;
                    } else {
                        throw error;
                    }
                }
            }

        }
    }

    /**
     * Start downloading the remote resource. The target object should not be accessed until after calling waitUntilCompleted().
     */
    @Override
    public void run() {
        synchronized (stateLock) {
            if (started) {
                return;
            } else {
                started = true;
                running = true;
            }
        }

        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        BufferedReader br = null;

        try {
            /* open connection to the URL */
            checkState();
            progressString = "Opening connection to remote resource";
            progressUpdated = true;

            final URLConnection link;

            try {
                link = url.openConnection();
                link.connect();
            } catch (Exception e) {
                progressString = "Failed to open connection to remote resource";
                progressUpdated = true;
                throw e;
            }

            /* get length of the remote resource */
            checkState();
            progressString = "Getting length of remote resource";
            progressUpdated = true;

            /* get size of webpage in bytes; -1 if unknown */
            final int length = link.getContentLength();

            synchronized (lengthLock) {
                totalLength = length;
            }

            progressUpdated = true;

            /* open input stream to remote resource */
            checkState();
            progressString = "Opening input stream to remote resource";
            progressUpdated = true;

            InputStream input = null;

            try {

                if (totalLength < 1) {

                    // load with http Request
                    HttpGet httppost = new HttpGet(url.toString());

                    // Execute HTTP Post Request
                    try {
                        HttpParams httpParameters = new BasicHttpParams();
                        HttpConnectionParams.setConnectionTimeout(httpParameters, HttpUtils.conectionTimeout);
                        HttpConnectionParams.setSoTimeout(httpParameters, HttpUtils.socketTimeout);
                        DefaultHttpClient httpClient = new DefaultHttpClient(httpParameters);
                        HttpResponse response = httpClient.execute(httppost);
                        input = response.getEntity().getContent();
                    } catch (ConnectTimeoutException e1) {

                        e1.printStackTrace();
                    } catch (ClientProtocolException e1) {

                        e1.printStackTrace();
                    } catch (IOException e1) {

                        e1.printStackTrace();
                    }

                } else {
                    input = link.getInputStream();
                }

                if (target instanceof File) {
                    bis = new BufferedInputStream(input);
                } else if (target instanceof StringBuilder) {
                    final String contentType = link.getContentType().toLowerCase(Locale.ENGLISH);

                    /* look for charset, if specified */
                    String charset = null;
                    final Matcher m = Pattern.compile(".*charset[\\s]*=([^;]++).*").matcher(contentType);

                    if (m.find()) {
                        charset = m.group(1).trim();
                    }

                    if ((charset != null) && charset.length() > 0) {
                        try {
                            br = new BufferedReader(new InputStreamReader(input, charset));
                        } catch (Exception e) {
                            br = null;
                        }
                    }

                    if (br == null) {
                        br = new BufferedReader(new InputStreamReader(input));
                    }
                }
            } catch (Exception e) {
                progressString = "Failed to open input stream to remote resource";
                progressUpdated = true;
                throw e;
            }

            /* open output stream, if necessary */
            if (target instanceof File) {
                checkState();
                progressString = "Opening output stream to local file";
                progressUpdated = true;

                try {
                    /* create parent directories, if necessary */
                    final File f = (File) target;
                    final File parent = f.getParentFile();

                    if ((parent != null) && !parent.exists()) {
                        parent.mkdirs();
                    }

                    bos = new BufferedOutputStream(f.getFileOutputStream());
                } catch (Exception e) {
                    progressString = "Failed to open output stream to local file";
                    progressUpdated = true;
                    throw e;
                }
            }

            /* download remote resource iteratively */
            progressString = "Downloading";
            progressUpdated = true;

            try {
                if (target instanceof File) {
                    final byte[] byteBuffer = new byte[BUFFER_SIZE];

                    while (true) {
                        checkState();
                        final int byteCount = bis.read(byteBuffer, 0, BUFFER_SIZE);

                        /* check for end-of-stream */
                        if (byteCount == -1) {
                            break;
                        }

                        bos.write(byteBuffer, 0, byteCount);

                        synchronized (lengthLock) {
                            downloadedLength += byteCount;
                        }

                        progressUpdated = true;
                    }
                } else if (target instanceof StringBuilder) {
                    final char[] charBuffer = new char[BUFFER_SIZE];
                    final StringBuilder sb = (StringBuilder) target;

                    while (true) {
                        checkState();
                        final int charCount = br.read(charBuffer, 0, BUFFER_SIZE);

                        /* check for end-of-stream */
                        if (charCount == -1) {
                            break;
                        }

                        sb.append(charBuffer, 0, charCount);

                        synchronized (lengthLock) {
                            downloadedLength += charCount; /* may be inaccurate because byte != char */
                        }

                        progressUpdated = true;
                    }
                }
            } catch (Exception e) {
                progressString = "Failed to download remote resource";
                progressUpdated = true;
                throw e;
            }

            /* download completed successfully */
            progressString = "Download completed";
            progressUpdated = true;
        } catch (Exception e) {
            error = e;
        } finally {
            /* clean-up */
            for (Closeable c : new Closeable[] { bis, br, bos }) {
                if (c != null) {
                    try {
                        c.close();
                    } catch (Exception e) {
                        /* ignore */
                    }
                }
            }

            synchronized (stateLock) {
                running = false;
                completed = true;
            }
        }
    }

    /**
     * Check if the downloader state has been modified. This method blocks if the download has been paused, unless it is resumed or
     * cancelled. An exception is thrown if the download is cancelled.
     * 
     * @throws java.lang.Exception
     *             if the download is cancelled
     */
    private void checkState() throws Exception {
        while (true) {
            synchronized (stateLock) {
                if (cancelled) {
                    progressString = "Download cancelled";
                    progressUpdated = true;
                    throw new Exception("Download cancelled");
                }

                if (running) {
                    return;
                }
            }

        }
    }
}