com.chiorichan.updater.Download.java Source code

Java tutorial

Introduction

Here is the source code for com.chiorichan.updater.Download.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2016 Chiori Greene a.k.a. Chiori-chan <me@chiorichan.com>
 * All Right Reserved.
 */
package com.chiorichan.updater;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.io.IOUtils;

import com.chiorichan.lang.DownloadDeniedException;
import com.chiorichan.lang.DownloadException;
import com.chiorichan.util.NetworkFunc;

public class Download implements Runnable {
    private class MonitorThread extends Thread {
        private final ReadableByteChannel rbc;
        private final Thread downloadThread;
        private long last = System.currentTimeMillis();

        MonitorThread(Thread downloadThread, ReadableByteChannel rbc) {
            super("Download Monitor Thread");
            this.setDaemon(true);
            this.rbc = rbc;
            this.downloadThread = downloadThread;
        }

        @Override
        public void run() {
            while (!this.isInterrupted()) {
                long diff = outFile.length() - downloaded;
                downloaded = outFile.length();
                if (diff == 0) {
                    if (System.currentTimeMillis() - last > TIMEOUT) {
                        if (listener != null)
                            listener.stateChanged("Download Failed", getProgress());
                        try {
                            rbc.close();
                            downloadThread.interrupt();
                        } catch (Exception ignore) {
                            // We catch all exceptions here, because ReadableByteChannel is AWESOME
                            // and was throwing NPE's sometimes when we tried to close it after
                            // the connection broke.
                        }
                        return;
                    }
                } else
                    last = System.currentTimeMillis();

                stateChanged();
                try {
                    sleep(50);
                } catch (InterruptedException ignore) {
                    return;
                }
            }
        }
    }

    public enum Result {
        SUCCESS, FAILURE, PERMISSION_DENIED,
    }

    private static class StreamThread extends Thread {
        private final URLConnection urlconnection;
        private final AtomicReference<InputStream> is;
        public final AtomicBoolean permDenied = new AtomicBoolean(false);

        StreamThread(URLConnection urlconnection, AtomicReference<InputStream> is) {
            this.urlconnection = urlconnection;
            this.is = is;
        }

        @Override
        public void run() {
            try {
                is.set(urlconnection.getInputStream());
            } catch (SocketException e) {
                if (e.getMessage().equalsIgnoreCase("Permission denied: connect"))
                    permDenied.set(true);
            } catch (IOException ignore) {
            }
        }
    }

    private static final long TIMEOUT = 30000;
    private final URL url;
    private long size = -1;
    private long downloaded = 0;
    private final String outPath;
    private final String name;
    private DownloadListener listener;

    private Result result = Result.FAILURE;

    private File outFile = null;

    private Exception exception = null;

    public Download(URL url, String name, String outPath) throws MalformedURLException {
        this.url = url;
        this.outPath = outPath;
        this.name = name;
    }

    protected InputStream getConnectionInputStream(final URLConnection urlconnection) throws DownloadException {
        final AtomicReference<InputStream> is = new AtomicReference<InputStream>();

        for (int j = 0; j < 3 && is.get() == null; j++) {
            StreamThread stream = new StreamThread(urlconnection, is);
            stream.start();
            int iterationCount = 0;
            while (is.get() == null && iterationCount++ < 5)
                try {
                    stream.join(1000L);
                } catch (InterruptedException ignore) {
                }

            if (stream.permDenied.get())
                throw new DownloadDeniedException("Permission denied!");

            if (is.get() != null)
                break;
            try {
                stream.interrupt();
                stream.join();
            } catch (InterruptedException ignore) {
            }
        }

        if (is.get() == null)
            throw new DownloadException("Unable to download file from " + urlconnection.getURL());
        return new BufferedInputStream(is.get());
    }

    public Exception getException() {
        return exception;
    }

    public File getOutFile() {
        return outFile;
    }

    public float getProgress() {
        return (float) downloaded / size * 100;
    }

    public Result getResult() {
        return result;
    }

    @Override
    public void run() {
        ReadableByteChannel rbc = null;
        FileOutputStream fos = null;
        try {
            HttpURLConnection conn = NetworkFunc.openHttpConnection(url);
            int response = conn.getResponseCode();
            int responseFamily = response / 100;

            if (responseFamily == 3)
                throw new DownloadException(
                        "The server issued a redirect response which the Updater failed to follow.");
            else if (responseFamily != 2)
                throw new DownloadException("The server issued a " + response + " response code.");

            InputStream in = getConnectionInputStream(conn);

            size = conn.getContentLength();
            outFile = new File(outPath);
            outFile.delete();

            rbc = Channels.newChannel(in);
            fos = new FileOutputStream(outFile);

            stateChanged();

            Thread progress = new MonitorThread(Thread.currentThread(), rbc);
            progress.start();

            fos.getChannel().transferFrom(rbc, 0, size > 0 ? size : Integer.MAX_VALUE);
            in.close();
            rbc.close();
            progress.interrupt();
            if (size > 0) {
                if (size == outFile.length())
                    result = Result.SUCCESS;
            } else
                result = Result.SUCCESS;

            stateDone();
        } catch (DownloadDeniedException e) {
            exception = e;
            result = Result.PERMISSION_DENIED;
        } catch (DownloadException e) {
            exception = e;
            result = Result.FAILURE;
        } catch (Exception e) {
            exception = e;
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(fos);
            IOUtils.closeQuietly(rbc);
        }

        if (exception != null)
            AutoUpdater.getLogger().severe("Download Resulted in an Exception", exception);
    }

    public void setListener(DownloadListener listener) {
        this.listener = listener;
    }

    private void stateChanged() {
        if (listener != null)
            listener.stateChanged(name, getProgress());
    }

    private void stateDone() {
        if (listener != null) {
            listener.stateChanged("Download Done!", 100);
            listener.stateDone();
        }
    }
}