Package com.skcraft.launcher.install

Source Code of com.skcraft.launcher.install.HttpDownloader$HttpDownloadJob

/*
* SK's Minecraft Launcher
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
* Please see LICENSE.txt for license information.
*/

package com.skcraft.launcher.install;

import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.skcraft.concurrency.ProgressObservable;
import com.skcraft.launcher.util.HttpRequest;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.java.Log;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.logging.Level;

import static com.skcraft.launcher.util.SharedLocale._;

@Log
public class HttpDownloader implements Downloader {

    private final Random random = new Random();
    private final HashFunction hf = Hashing.sha1();

    private final File tempDir;
    @Getter @Setter private int threadCount = 6;
    @Getter @Setter private int retryDelay = 2000;
    @Getter @Setter private int tryCount = 3;

    private List<HttpDownloadJob> queue = new ArrayList<HttpDownloadJob>();
    private final Set<String> usedKeys = new HashSet<String>();

    private final List<HttpDownloadJob> running = new ArrayList<HttpDownloadJob>();
    private final List<HttpDownloadJob> failed = new ArrayList<HttpDownloadJob>();
    private long downloaded = 0;
    private long total = 0;
    private int left = 0;

    /**
     * Create a new downloader using the given executor.
     *
     * @param tempDir the temporary directory
     */
    public HttpDownloader(@NonNull File tempDir) {
        this.tempDir = tempDir;
    }

    /**
     * Make sure that we aren't re-using hash IDs.
     *
     * @param baseKey the key to make unique
     * @return a unique key
     */
    private String createUniqueKey(String baseKey) {
        String key = baseKey;
        int i = 0;
        while (usedKeys.contains(key)) {
            key = baseKey + "_" + (i++);
        }
        usedKeys.add(key);
        return key;
    }

    @Override
    public synchronized File download(@NonNull List<URL> urls, @NonNull String key, long size, String name) {
        if (urls.isEmpty()) {
            throw new IllegalArgumentException("Can't download empty list of URLs");
        }

        String hash = hf.hashString(Strings.nullToEmpty(key) + urls.get(0), Charsets.UTF_8).toString();
        hash = createUniqueKey(hash);
        File tempFile = new File(tempDir, hash.substring(0, 2) + "/" + hash);

        // If the file is already downloaded (such as from before), then don't re-download
        if (!tempFile.exists()) {
            total += size;
            left++;
            queue.add(new HttpDownloadJob(tempFile, urls, size, name != null ? name : tempFile.getName()));
        }

        return tempFile;
    }


    @Override
    public File download(URL url, String key, long size, String name) {
        List<URL> urls = new ArrayList<URL>();
        urls.add(url);
        return download(urls, key, size, name);
    }

    /**
     * Prevent further downloads from being queued and download queued files.
     *
     * @throws InterruptedException thrown on interruption
     * @throws IOException thrown on I/O error
     */
    public void execute() throws InterruptedException, IOException {
        synchronized (this) {
            queue = Collections.unmodifiableList(queue);
        }

        ListeningExecutorService executor = MoreExecutors.listeningDecorator(
                Executors.newFixedThreadPool(threadCount));

        try {
            List<ListenableFuture<?>> futures = new ArrayList<ListenableFuture<?>>();

            synchronized (this) {
                for (HttpDownloadJob job : queue) {
                    futures.add(executor.submit(job));
                }
            }

            try {
                Futures.allAsList(futures).get();
            } catch (ExecutionException e) {
                throw new IOException("Something went wrong", e);
            }

            synchronized (this) {
                if (failed.size() > 0) {
                    throw new IOException(failed.size() + " file(s) could not be downloaded");
                }
            }
        } finally {
            executor.shutdownNow();
        }
    }

    @Override
    public synchronized double getProgress() {
        if (total <= 0) {
            return -1;
        }

        long downloaded = this.downloaded;
        for (HttpDownloadJob job : running) {
            downloaded += Math.max(0, job.getProgress() * job.size);
        }
        return downloaded / (double) total;
    }

    @Override
    public synchronized String getStatus() {
        String failMessage = _("downloader.failedCount", failed.size());
        if (running.size() == 1) {
            return _("downloader.downloadingItem", running.get(0).getName()) +
                    "\n" + running.get(0).getStatus() +
                    "\n" + failMessage;
        } else if (running.size() > 0) {
            StringBuilder builder = new StringBuilder();
            for (HttpDownloadJob job : running) {
                builder.append("\n");
                builder.append(job.getStatus());
            }
            return _("downloader.downloadingList", queue.size(), left, failed.size()) +
                    builder.toString() +
                    "\n" + failMessage;
        } else {
            return _("downloader.noDownloads");
        }
    }

    public class HttpDownloadJob implements Runnable, ProgressObservable {
        private final File destFile;
        private final List<URL> urls;
        private final long size;
        @Getter private String name;
        private HttpRequest request;

        private HttpDownloadJob(File destFile, List<URL> urls, long size, String name) {
            this.destFile = destFile;
            this.urls = urls;
            this.size = size;
            this.name = name;
        }

        @Override
        public void run() {
            try {
                synchronized (HttpDownloader.this) {
                    running.add(this);
                }

                download();

                synchronized (HttpDownloader.this) {
                    downloaded += size;
                }
            } catch (IOException e) {
                synchronized (HttpDownloader.this) {
                    failed.add(this);
                }
            } catch (InterruptedException e) {
                log.info("Download of " + destFile + " was interrupted");
            } finally {
                synchronized (HttpDownloader.this) {
                    left--;
                    running.remove(this);
                }
            }
        }

        private void download() throws IOException, InterruptedException {
            log.log(Level.INFO, "Downloading " + destFile + " from " + urls);

            File destDir = destFile.getParentFile();
            File tempFile = new File(destDir, destFile.getName() + ".tmp");
            destDir.mkdirs();

            // Try to download
            download(tempFile);

            destFile.delete();
            if (!tempFile.renameTo(destFile)) {
                throw new IOException(String.format("Failed to rename %s to %s", tempFile, destFile));
            }
        }

        private void download(File file) throws IOException, InterruptedException {
            int trial = 0;
            boolean first = true;
            IOException lastException = null;

            do {
                for (URL url : urls) {
                    // Sleep between each trial
                    if (!first) {
                        Thread.sleep((long) (retryDelay / 2 + (random.nextDouble() * retryDelay)));
                    }
                    first = false;

                    try {
                        request = HttpRequest.get(url);
                        request.execute().expectResponseCode(200).saveContent(file);
                        return;
                    } catch (IOException e) {
                        lastException = e;
                        log.log(Level.WARNING, "Failed to download " + url, e);
                    }
                }
            } while (++trial < tryCount);

            throw new IOException("Failed to download from " + urls, lastException);
        }

        @Override
        public double getProgress() {
            HttpRequest request = this.request;
            return request != null ? request.getProgress() : -1;
        }

        @Override
        public String getStatus() {
            double progress = getProgress();
            if (progress >= 0) {
                return _("downloader.jobProgress", name, Math.round(progress * 100 * 100) / 100.0);
            } else {
                return _("downloader.jobPending", name);
            }
        }
    }

}
TOP

Related Classes of com.skcraft.launcher.install.HttpDownloader$HttpDownloadJob

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.