net.technicpack.launchercore.mirror.download.Download.java Source code

Java tutorial

Introduction

Here is the source code for net.technicpack.launchercore.mirror.download.Download.java

Source

/*
 * This file is part of Technic Launcher Core.
 * Copyright 2015 Syndicate, LLC
 *
 * Technic Launcher Core is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Technic Launcher Core 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,
 * as well as a copy of the GNU Lesser General Public License,
 * along with Technic Launcher Core.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.technicpack.launchercore.mirror.download;

import net.technicpack.launchercore.exception.DownloadException;
import net.technicpack.launchercore.exception.PermissionDeniedException;
import net.technicpack.launchercore.util.DownloadListener;
import net.technicpack.utilslib.Utils;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.net.*;
import java.nio.channels.Channels;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

public class Download implements Runnable {
    private static final long TIMEOUT = 30000;

    private URL url;
    private long size = -1;
    private long downloaded = 0;
    private String outPath;
    private String name;
    private DownloadListener listener;
    private Result result = Result.FAILURE;
    private File outFile = null;
    private Exception exception = null;

    private Object timeoutLock = new Object();
    private boolean isTimedOut = false;

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

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

    public Exception getException() {
        return exception;
    }

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

            if (responseFamily == 3) {
                String redirUrlText = conn.getHeaderField("Location");
                if (redirUrlText != null && !redirUrlText.isEmpty()) {
                    URL redirectUrl = null;
                    try {
                        redirectUrl = new URL(redirUrlText);
                    } catch (MalformedURLException ex) {
                        throw new DownloadException("Invalid Redirect URL: " + url, ex);
                    }

                    conn = Utils.openHttpConnection(redirectUrl);
                    response = conn.getResponseCode();
                    responseFamily = response / 100;
                }
            }

            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();

            synchronized (timeoutLock) {
                if (isTimedOut) {
                    return;
                }
            }

            if (size > 0) {
                if (size == outFile.length()) {
                    result = Result.SUCCESS;
                }
            } else {
                result = Result.SUCCESS;
            }
        } catch (ClosedByInterruptException ex) {
            result = Result.FAILURE;
            return;
        } catch (PermissionDeniedException 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);
        }
    }

    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 PermissionDeniedException("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());
    }

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

    private void timeout() {
        synchronized (timeoutLock) {
            isTimedOut = true;
        }
    }

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

    public Result getResult() {
        return result;
    }

    public File getOutFile() {
        return outFile;
    }

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

        public 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 class MonitorThread extends Thread {
        private final ReadableByteChannel rbc;
        private final Thread downloadThread;
        private long last = System.currentTimeMillis();

        public 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) {
                        try {
                            rbc.close();
                            timeout();
                        } 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,
    }
}