net.ftb.util.DownloadUtils.java Source code

Java tutorial

Introduction

Here is the source code for net.ftb.util.DownloadUtils.java

Source

/*
 * This file is part of FTB Launcher.
 *
 * Copyright  2012-2014, FTB Launcher Contributors <https://github.com/Slowpoke101/FTBLaunch/>
 * FTB Launcher is 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 net.ftb.util;

import static net.ftb.download.Locations.backupServers;
import static net.ftb.download.Locations.downloadServers;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Scanner;

import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import lombok.NonNull;
import net.ftb.data.Settings;
import net.ftb.download.Locations;
import net.ftb.gui.LaunchFrame;
import net.ftb.gui.dialogs.LoadingDialog;
import net.ftb.log.Logger;

import org.apache.commons.io.IOUtils;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import javax.imageio.ImageIO;

public class DownloadUtils extends Thread {

    /**
     * @param file - the name of the file, as saved to the repo (including extension)
     * @return - the direct link
     */
    public static String getCreeperhostLink(String file) {
        String resolved = (downloadServers.containsKey(Settings.getSettings().getDownloadServer()))
                ? "http://" + downloadServers.get(Settings.getSettings().getDownloadServer())
                : Locations.masterRepo;
        resolved += "/FTB2/" + file;
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) new URL(resolved).openConnection();
            connection.setRequestProperty("Cache-Control", "no-transform");
            connection.setRequestMethod("HEAD");
            for (String server : downloadServers.values()) {
                if (connection.getResponseCode() != 200) {
                    if (!server.contains("creeper")) {
                        file = file.replaceAll("%5E", "/");
                    }

                    resolved = "http://" + server + "/FTB2/" + file;
                    connection = (HttpURLConnection) new URL(resolved).openConnection();
                    connection.setRequestProperty("Cache-Control", "no-transform");
                    connection.setRequestMethod("HEAD");
                } else {
                    break;
                }
            }
        } catch (IOException e) {
        }
        connection.disconnect();
        return resolved;
    }

    /**
     * @param file - the name of the file, as saved to the repo (including extension)
     * @param backupLink - the link of the location to backup to if the repo copy isn't found
     * @return - the direct static link or the backup link if the file isn't found
     */
    public static String getStaticCreeperhostLinkOrBackup(String file, String backupLink) {
        String resolved = (downloadServers.containsKey(Settings.getSettings().getDownloadServer()))
                ? "http://" + downloadServers.get(Settings.getSettings().getDownloadServer())
                : Locations.masterRepo;
        resolved += "/FTB2/static/" + file;
        HttpURLConnection connection = null;
        boolean good = false;
        try {
            connection = (HttpURLConnection) new URL(resolved).openConnection();
            connection.setRequestProperty("Cache-Control", "no-transform");
            connection.setRequestMethod("HEAD");
            if (connection.getResponseCode() != 200) {
                for (String server : downloadServers.values()) {
                    if (connection.getResponseCode() != 200) {
                        resolved = "http://" + server + "/FTB2/static/" + file;
                        connection = (HttpURLConnection) new URL(resolved).openConnection();
                        connection.setRequestProperty("Cache-Control", "no-transform");
                        connection.setRequestMethod("HEAD");
                    } else {
                        if (connection.getResponseCode() == 200)
                            good = true;
                        break;
                    }
                }
            } else if (connection.getResponseCode() == 200) {
                good = true;
            }
        } catch (IOException e) {
        }
        connection.disconnect();
        if (good)
            return resolved;
        else {
            Logger.logWarn("Using backupLink for " + file);
            return backupLink;
        }
    }

    /**
     * @param file - the name of the file, as saved to the repo (including extension)
     * @return - the direct link
     */
    public static String getStaticCreeperhostLink(String file) {
        String resolved = (downloadServers.containsKey(Settings.getSettings().getDownloadServer()))
                ? "http://" + downloadServers.get(Settings.getSettings().getDownloadServer())
                : Locations.masterRepo;
        resolved += "/FTB2/static/" + file;
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) new URL(resolved).openConnection();
            connection.setRequestProperty("Cache-Control", "no-transform");
            connection.setRequestMethod("HEAD");
            if (connection.getResponseCode() != 200) {
                for (String server : downloadServers.values()) {
                    if (connection.getResponseCode() != 200) {
                        resolved = "http://" + server + "/FTB2/static/" + file;
                        connection = (HttpURLConnection) new URL(resolved).openConnection();
                        connection.setRequestProperty("Cache-Control", "no-transform");
                        connection.setRequestMethod("HEAD");
                    } else {
                        break;
                    }
                }
            }
        } catch (IOException e) {
        }
        connection.disconnect();
        return resolved;
    }

    /**
     * @param file - file on the repo in static
     * @return boolean representing if the file exists 
     */
    public static boolean staticFileExists(String file) {
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(getStaticCreeperhostLink(file))
                    .openConnection();
            connection.setRequestProperty("Cache-Control", "no-transform");
            connection.setRequestMethod("HEAD");
            return (connection.getResponseCode() == 200);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * @param file - file on the repo
     * @return boolean representing if the file exists 
     */
    public static boolean fileExists(String file) {
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(Locations.masterRepo + "/FTB2/" + file)
                    .openConnection();
            connection.setRequestProperty("Cache-Control", "no-transform");
            connection.setRequestMethod("HEAD");
            return (connection.getResponseCode() == 200);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * @param repoURL - URL on the repo
     * @param fullDebug - should this dump the full cloudflare debug info in the console
     * @return boolean representing if the file exists
     */
    public static boolean CloudFlareInspector(String repoURL, boolean fullDebug) {
        try {
            boolean ret;
            HttpURLConnection connection = (HttpURLConnection) new URL(repoURL + "cdn-cgi/trace").openConnection();
            if (!fullDebug)
                connection.setRequestMethod("HEAD");
            Logger.logInfo("CF-RAY: " + connection.getHeaderField("CF-RAY"));
            if (fullDebug)
                Logger.logInfo("CF Debug Info: " + connection.getContent().toString());
            ret = connection.getResponseCode() == 200;
            IOUtils.close(connection);
            return ret;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Downloads data from the given URL and saves it to the given file
     * @param filename - String of destination
     * @param urlString - http location of file to download
     */
    public static void downloadToFile(String filename, String urlString) throws IOException {
        downloadToFile(new URL(urlString), new File(filename));
    }

    /**
     * Downloads data from the given URL and saves it to the given file
     * @param url The url to download from
     * @param file The file to save to.
     *
     * TODO: how to handle partial downloads? Old file is overwritten as soon as FileOutputStream is created.
     */
    public static void downloadToFile(URL url, File file) throws IOException {
        file.getParentFile().mkdirs();
        ReadableByteChannel rbc = Channels.newChannel(url.openStream());
        FileOutputStream fos = new FileOutputStream(file);
        fos.getChannel().transferFrom(rbc, 0, 1 << 24);
        fos.close();
    }

    /**
     * Download data from the given URL and saves it to the given file, tries to download attempts times
     * @param url The url to download from
     * @param file The file to save to
     * @param attempts attempts to download file if downloadToFile(URL url, File file) fails
     */
    public static void downloadToFile(URL url, File file, int attempts) {
        int attempt = 0;
        boolean success = false;
        Exception reason = null;
        while ((attempt < attempts) && !success) {
            try {
                success = true;
                DownloadUtils.downloadToFile(url, file);
            } catch (Exception e) {
                success = false;
                reason = e;
                attempt++;
            }
            if (attempt == attempts && !success) {
                Logger.logError("library JSON download failed", reason);
                //TODO: check fail reason and delete malformed JSON
                return;
            }
        }
    }

    /**
     * Used to download pack images from repo to hard disk
     * @param file Name of the image
     * @param location Image save location in hard disk
     * @param type image type to use when saving
     */
    public static void saveImage(String file, File location, String type) {
        // stupid code: tries to find working server twice.
        if (DownloadUtils.staticFileExists(file)) {
            try {
                URL url_ = new URL(DownloadUtils.getStaticCreeperhostLink(file));
                BufferedImage tempImg = ImageIO.read(url_);
                ImageIO.write(tempImg, type, new File(location, file));
                tempImg.flush();
            } catch (IOException e) {
                Logger.logWarn("image download/save failed", e);
                new File(location, file).delete();
            }
        }
    }

    /**
     * Checks the file for corruption.
     * @param file - File to check
     * @param md5 - remote MD5 to compare against
     * @return boolean representing if it is valid
     * @throws IOException 
     */
    public static boolean isValid(File file, String md5) throws IOException {
        String result = fileMD5(file);
        // Logger.logInfo("OLDHASHING: " + fileHash(file, "md5"));
        Logger.logInfo("Local: " + result.toUpperCase());
        Logger.logInfo("Remote: " + md5.toUpperCase());
        return md5.equalsIgnoreCase(result);
    }

    /**
     * Checks the file for corruption.
     * @param file - File to check
     * @param url - base url to grab md5 with old method
     * @return boolean representing if it is valid
     * @throws IOException 
     */
    public static boolean backupIsValid(File file, String url) throws IOException {
        Logger.logInfo("Issue with new md5 method, attempting to use backup method.");
        String content = null;
        Scanner scanner = null;
        String resolved = (downloadServers.containsKey(Settings.getSettings().getDownloadServer()))
                ? "http://" + downloadServers.get(Settings.getSettings().getDownloadServer())
                : Locations.masterRepo;
        resolved += "/md5/FTB2/" + url;
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) new URL(resolved).openConnection();
            connection.setRequestProperty("Cache-Control", "no-transform");
            int response = connection.getResponseCode();
            if (response == 200) {
                scanner = new Scanner(connection.getInputStream());
                scanner.useDelimiter("\\Z");
                content = scanner.next();
            }
            if (response != 200 || (content == null || content.isEmpty())) {
                for (String server : backupServers.values()) {
                    resolved = "http://" + server + "/md5/FTB2/" + url.replace("/", "%5E");
                    connection = (HttpURLConnection) new URL(resolved).openConnection();
                    connection.setRequestProperty("Cache-Control", "no-transform");
                    response = connection.getResponseCode();
                    if (response == 200) {
                        scanner = new Scanner(connection.getInputStream());
                        scanner.useDelimiter("\\Z");
                        content = scanner.next();
                        if (content != null && !content.isEmpty()) {
                            break;
                        }
                    }
                }
            }
        } catch (IOException e) {
        } finally {
            connection.disconnect();
            if (scanner != null) {
                scanner.close();
            }
        }
        String result = fileMD5(file);
        Logger.logInfo("Local: " + result.toUpperCase());
        Logger.logInfo("Remote: " + content.toUpperCase());
        return content.equalsIgnoreCase(result);
    }

    /**
     * Gets the md5 of the downloaded file
     * @param file - File to check
     * @return - string of file's md5
     * @throws IOException 
     */
    public static String fileMD5(File file) throws IOException {
        if (file.exists()) {
            return Files.hash(file, Hashing.md5()).toString();
            //FileInputStream fis = new FileInputStream(file);
            //String result = DigestUtils.md5Hex(fis);
            //fis.close();
            //return result;
        } else
            return "";
    }

    public static String fileSHA(File file) throws IOException {
        if (file.exists()) {
            return Files.hash(file, Hashing.sha1()).toString();
            //FileInputStream fis = new FileInputStream(file);
            //String result = DigestUtils.sha1Hex(fis).toLowerCase();
            //fis.close();
            //return result;
        } else
            return "";
        //return fileHash(file, "sha1").toLowerCase();
    }

    public static String fileHash(File file, String type) throws IOException {
        if (!file.exists()) {
            return "";
        }
        if (type.equalsIgnoreCase("md5"))
            return fileMD5(file);
        if (type.equalsIgnoreCase("sha1"))
            return fileSHA(file);
        URL fileUrl = file.toURI().toURL();
        MessageDigest dgest = null;
        try {
            dgest = MessageDigest.getInstance(type);
        } catch (NoSuchAlgorithmException e) {
        }
        InputStream str = fileUrl.openStream();
        byte[] buffer = new byte[65536];
        int readLen;
        while ((readLen = str.read(buffer, 0, buffer.length)) != -1) {
            dgest.update(buffer, 0, readLen);
        }
        str.close();
        Formatter fmt = new Formatter();
        for (byte b : dgest.digest()) {
            fmt.format("%02X", b);
        }
        String result = fmt.toString();
        fmt.close();
        return result;
    }

    /**
     * Used to load all available download servers in a thread to prevent wait.
     */
    @Override
    public void run() {
        setName("DownloadUtils");
        if (!Locations.hasDLInitialized) {
            Benchmark.start("DlUtils");
            Logger.logInfo("DownloadUtils.run() starting");
            downloadServers.put("Automatic", Locations.masterRepoNoHTTP);
            Random r = new Random();
            double choice = r.nextDouble();
            try { // Super catch-all to ensure the launcher always renders
                try {
                    // Fetch the percentage json first
                    String json = IOUtils.toString(new URL(Locations.masterRepo + "/FTB2/static/balance.json"));
                    JsonElement element = new JsonParser().parse(json);

                    if (element != null && element.isJsonObject()) {
                        JsonObject jso = element.getAsJsonObject();
                        if (jso != null && jso.get("minUsableLauncherVersion") != null) {
                            LaunchFrame.getInstance().minUsable = jso.get("minUsableLauncherVersion").getAsInt();
                        }
                        if (jso != null && jso.get("chEnabled") != null) {
                            Locations.chEnabled = jso.get("chEnabled").getAsBoolean();
                        }
                        if (jso != null && jso.get("repoSplitCurse") != null) {
                            JsonElement e = jso.get("repoSplitCurse");
                            Logger.logDebug("Balance Settings: " + e.getAsDouble() + " > " + choice);
                            if (e != null && e.getAsDouble() > choice) {
                                Logger.logInfo("Balance has selected Automatic:CurseCDN");
                            } else {
                                Logger.logInfo("Balance has selected Automatic:CreeperRepo");
                                Locations.masterRepoNoHTTP = Locations.chRepo.replaceAll("http://", "");
                                Locations.masterRepo = Locations.chRepo;
                                Locations.primaryCH = true;
                                downloadServers.remove("Automatic");
                                downloadServers.put("Automatic", Locations.masterRepoNoHTTP);
                            }
                        }
                    }
                    Benchmark.logBenchAs("DlUtils", "Download Utils Bal");
                    if (Locations.chEnabled) {
                        // Fetch servers from creeperhost using edges.json first
                        parseJSONtoMap(new URL(Locations.chRepo + "/edges.json"), "CH", downloadServers, false,
                                "edges.json");
                        Benchmark.logBenchAs("DlUtils", "Download Utils CH");
                    }
                    // Fetch servers list from curse using edges.json second
                    parseJSONtoMap(new URL(Locations.curseRepo + "/edges.json"), "Curse", downloadServers, false,
                            "edges.json");
                    Benchmark.logBenchAs("DlUtils", "Download Utils Curse");
                    LoadingDialog.setProgress(80);
                } catch (IOException e) {
                    int i = 10;

                    // If fetching edges.json failed, assume new. is inaccessible
                    // Try alternate mirrors from the cached server list in resources
                    downloadServers.clear();

                    Logger.logInfo("Primary mirror failed, Trying alternative mirrors");
                    LoadingDialog.setProgress(i);
                    parseJSONtoMap(this.getClass().getResource("/edges.json"), "Backup", downloadServers, true,
                            "edges.json");
                }
                LoadingDialog.setProgress(90);

                if (downloadServers.size() == 0) {
                    Logger.logError(
                            "Could not find any working mirrors! If you are running a software firewall please allow the FTB Launcher permission to use the internet.");

                    // Fall back to new. (old system) on critical failure
                    downloadServers.put("Automatic", Locations.masterRepoNoHTTP);
                } else if (!downloadServers.containsKey("Automatic")) {
                    // Use a random server from edges.json as the Automatic server
                    int index = (int) (Math.random() * downloadServers.size());
                    List<String> keys = Lists.newArrayList(downloadServers.keySet());
                    String defaultServer = downloadServers.get(keys.get(index));
                    downloadServers.put("Automatic", defaultServer);
                    Logger.logInfo("Selected " + keys.get(index) + " mirror for Automatic assignment");
                }
            } catch (Exception e) {
                Logger.logError("Error while selecting server", e);
                downloadServers.clear();
                downloadServers.put("Automatic", Locations.masterRepoNoHTTP);
            }

            LoadingDialog.setProgress(100);
            Locations.serversLoaded = true;

            // This line absolutely must be hit, or the console will not be shown
            // and the user/we will not even know why an error has occurred. 
            Logger.logInfo("DL ready");

            String selectedMirror = Settings.getSettings().getDownloadServer();
            String selectedHost = downloadServers.get(selectedMirror);
            String resolvedIP = "UNKNOWN";
            String resolvedHost = "UNKNOWN";
            String resolvedMirror = "UNKNOWN";

            try {
                InetAddress ipAddress = InetAddress.getByName(selectedHost);
                resolvedIP = ipAddress.getHostAddress();
            } catch (UnknownHostException e) {
                Logger.logError("Failed to resolve selected mirror: " + e.getMessage());
            }

            try {
                for (String key : downloadServers.keySet()) {
                    if (key.equals("Automatic"))
                        continue;

                    InetAddress host = InetAddress.getByName(downloadServers.get(key));

                    if (resolvedIP.equalsIgnoreCase(host.getHostAddress())) {
                        resolvedMirror = key;
                        resolvedHost = downloadServers.get(key);
                        break;
                    }
                }
            } catch (UnknownHostException e) {
                Logger.logError("Failed to resolve mirror: " + e.getMessage());
            }

            Logger.logInfo("Using download server " + selectedMirror + ":" + resolvedMirror + " on host "
                    + resolvedHost + " (" + resolvedIP + ")");
            Benchmark.logBenchAs("DlUtils", "Download Utils Init");
        }
        Locations.hasDLInitialized = true;
    }

    /**
     * method to parse & test if needed server listing
     * @param u - URL of file to download & parse
     * @param name - json server's nickname for use in error reports
     * @param h - map to be written to
     * @param testEntries - should the locations be tested?
     * @param location - location to test on the repo ex: edges.json would test ${repoURL}/edges.json
     */
    @NonNull
    public void parseJSONtoMap(URL u, String name, HashMap<String, String> h, boolean testEntries,
            String location) {
        try {
            String json = IOUtils.toString(u);
            JsonElement element = new JsonParser().parse(json);
            int i = 10;
            if (element.isJsonObject()) {
                JsonObject jso = element.getAsJsonObject();
                for (Entry<String, JsonElement> e : jso.entrySet()) {
                    if (testEntries) {
                        try {
                            Logger.logInfo("Testing Server:" + e.getKey());
                            //test that the server will properly handle file DL's if it doesn't throw an error the web daemon should be functional
                            IOUtils.toString(new URL("http://" + e.getValue().getAsString() + "/" + location));
                            h.put(e.getKey(), e.getValue().getAsString());
                        } catch (Exception ex) {
                            Logger.logWarn(
                                    (e.getValue().getAsString().contains("creeper") ? "CreeperHost" : "Curse")
                                            + " Server: " + e.getKey() + " was not accessible, ignoring."
                                            + ex.getMessage());
                        }

                        if (i < 90)
                            i += 10;
                        LoadingDialog.setProgress(i);
                    } else {
                        h.put(e.getKey(), e.getValue().getAsString());
                    }
                }
            }
        } catch (Exception e2) {
            Logger.logError("Error parsing JSON " + name + " " + location, e2);
        }
    }
}