/*
* ATLauncher - https://github.com/ATLauncher/ATLauncher
* Copyright (C) 2013 ATLauncher
*
* 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/>.
*/
package com.atlauncher.data;
import com.atlauncher.App;
import com.atlauncher.LogManager;
import com.atlauncher.utils.Utils;
import com.atlauncher.workers.InstanceInstaller;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
public class Downloadable {
private String beforeURL;
private String url;
private File file;
private File oldFile;
private String hash;
private int size;
private HttpURLConnection connection;
private InstanceInstaller instanceInstaller;
private boolean isATLauncherDownload;
private File copyTo;
private boolean actuallyCopy;
private int attempts = 0;
private List<Server> servers;
private Server server;
private boolean checkForNewness = false;
public Downloadable(String url, File file, String hash, int size, InstanceInstaller instanceInstaller, boolean
isATLauncherDownload, File copyTo, boolean actuallyCopy) {
if (isATLauncherDownload) {
this.servers = new ArrayList<Server>(App.settings.getServers());
this.server = this.servers.get(0);
for (Server server : this.servers) {
if (server.getName().equals(App.settings.getServer().getName())) {
this.server = server;
break;
}
}
this.url = this.server.getFileURL(url);
} else {
this.url = url;
}
this.beforeURL = url;
this.file = file;
this.hash = hash;
this.size = size;
this.instanceInstaller = instanceInstaller;
this.isATLauncherDownload = isATLauncherDownload;
this.copyTo = copyTo;
this.actuallyCopy = actuallyCopy;
}
public Downloadable(String url, File file, String hash, int size, InstanceInstaller instanceInstaller, boolean
isATLauncherDownload) {
this(url, file, hash, size, instanceInstaller, isATLauncherDownload, null, false);
}
public Downloadable(String url, File file, String hash, InstanceInstaller instanceInstaller, boolean
isATLauncherDownload) {
this(url, file, hash, -1, instanceInstaller, isATLauncherDownload, null, false);
}
public Downloadable(String url, File file) {
this(url, file, null, -1, null, false, null, false);
}
public Downloadable(String url, boolean isATLauncherDownload) {
this(url, null, null, -1, null, isATLauncherDownload, null, false);
}
public String getFilename() {
if (this.copyTo == null) {
return this.file.getName();
}
return this.copyTo.getName();
}
public void checkForNewness() {
this.checkForNewness = true;
}
public boolean isMD5() {
return hash == null || hash.length() != 40;
}
public String getHashFromURL() throws IOException {
String etag = null;
etag = getConnection().getHeaderField("ETag");
if (etag == null) {
etag = getConnection().getHeaderField(Constants.LAUNCHER_NAME + "-MD5");
}
if (etag == null) {
return "-";
}
if ((etag.startsWith("\"")) && (etag.endsWith("\""))) {
etag = etag.substring(1, etag.length() - 1);
}
if (etag.matches("[A-Za-z0-9]{32}")) {
return etag;
} else {
return "-";
}
}
public int getFilesize() {
if (this.size == -1) {
int size = getConnection().getContentLength();
if (size == -1) {
this.size = 0;
} else {
this.size = size;
}
}
return this.size;
}
public boolean needToDownload() {
if (this.file == null) {
return true;
}
if (this.file.exists()) {
if (this.checkForNewness) {
if (this.getConnection() != null && this.getFile().length() == this.getFilesize()) {
return false;
}
}
if (isMD5()) {
if (Utils.getMD5(this.file).equalsIgnoreCase(getHash())) {
return false;
}
} else {
if (Utils.getSHA1(this.file).equalsIgnoreCase(getHash())) {
return false;
}
}
}
return true;
}
public void copyFile() {
if (this.copyTo != null && this.actuallyCopy) {
if (this.copyTo.exists()) {
Utils.delete(this.copyTo);
}
new File(this.copyTo.getAbsolutePath().substring(0, this.copyTo.getAbsolutePath().lastIndexOf(File
.separatorChar))).mkdirs();
Utils.copyFile(this.file, this.copyTo, true);
}
}
public String getHash() {
if (this.hash == null || this.hash.isEmpty()) {
try {
this.hash = getHashFromURL();
} catch (IOException e) {
LogManager.logStackTrace(e);
this.hash = "-";
this.connection = null;
}
}
return this.hash;
}
public File getFile() {
return this.file;
}
public File getCopyToFile() {
return this.copyTo;
}
public boolean isGziped() {
if (getConnection().getContentEncoding() == null) {
return false;
} else if (getConnection().getContentEncoding().equalsIgnoreCase("gzip")) {
return true;
} else {
return false;
}
}
private HttpURLConnection getConnection() {
if (this.instanceInstaller != null) {
if (this.instanceInstaller.isCancelled()) {
return null;
}
}
if (this.connection == null) {
LogManager.debug("Opening connection to " + this.url, 3);
try {
String url = this.url;
Integer numberOfRedirects = 0;
while (numberOfRedirects < 5)
{
URL resourceUrl = new URL(url);
if (App.settings.getEnableProxy()) {
this.connection = (HttpURLConnection) resourceUrl.openConnection(App.settings.getProxy());
} else {
this.connection = (HttpURLConnection) resourceUrl.openConnection();
}
this.connection.setInstanceFollowRedirects(true);
this.connection.setUseCaches(false);
this.connection.setDefaultUseCaches(false);
if (App.useGzipForDownloads) {
this.connection.setRequestProperty("Accept-Encoding", "gzip");
}
this.connection.setRequestProperty("User-Agent", App.settings.getUserAgent());
this.connection.setRequestProperty("Cache-Control", "no-store,max-age=0,no-cache");
this.connection.setRequestProperty("Expires", "0");
this.connection.setRequestProperty("Pragma", "no-cache");
this.connection.connect();
// check for redirections
switch (this.connection.getResponseCode()) {
case HttpURLConnection.HTTP_MOVED_PERM:
case HttpURLConnection.HTTP_MOVED_TEMP:
case 307:
String location = this.connection.getHeaderField("Location");
URL base = new URL(url);
URL next = new URL(base, location); // Deal with relative URLs
url = next.toExternalForm();
numberOfRedirects++;
continue;
}
break;
}
if (this.connection.getResponseCode() / 100 != 2) {
throw new IOException(this.url + " returned response code " + this.connection.getResponseCode() +
(this.connection.getResponseMessage() != null ? " with message of " + this.connection
.getResponseMessage() : ""));
}
LogManager.debug("Connection opened to " + this.url, 3);
} catch (IOException e) {
LogManager.debug("Exception when opening connection to " + this.url, 3);
LogManager.logStackTrace(e);
if (this.isATLauncherDownload) {
if (getNextServer()) {
this.url = server.getFileURL(this.beforeURL);
this.connection = null;
return getConnection();
} else {
LogManager.error("Failed to download " + this.beforeURL + " from all " + Constants.LAUNCHER_NAME + " servers. " +
"Cancelling install!");
if (this.instanceInstaller != null) {
instanceInstaller.cancel(true);
}
}
}
}
}
return this.connection;
}
private void downloadFile(boolean downloadAsLibrary) {
if (instanceInstaller != null) {
if (instanceInstaller.isCancelled()) {
return;
}
}
InputStream in = null;
FileOutputStream writer = null;
try {
if (isGziped() && App.useGzipForDownloads) {
in = new GZIPInputStream(getConnection().getInputStream());
} else {
in = getConnection().getInputStream();
}
writer = new FileOutputStream(this.file);
byte[] buffer = new byte[2048];
int bytesRead = 0;
while ((bytesRead = in.read(buffer)) > 0) {
writer.write(buffer, 0, bytesRead);
buffer = new byte[2048];
if (this.instanceInstaller != null && downloadAsLibrary && getFilesize() != 0) {
this.instanceInstaller.addDownloadedBytes(bytesRead);
}
}
} catch (SocketException e) {
LogManager.error("Failed to download " + this.url + " due to SocketException!");
// Connection reset. Close connection and try again
LogManager.logStackTrace(e);
this.connection.disconnect();
this.connection = null;
if (this.oldFile != null && this.oldFile.exists()) {
Utils.moveFile(this.oldFile, this.file, true);
}
} catch (IOException e) {
LogManager.error("Failed to download " + this.url + " due to IOException!");
LogManager.logStackTrace(e);
if (this.oldFile != null && this.oldFile.exists()) {
Utils.moveFile(this.oldFile, this.file, true);
}
} finally {
try {
if (writer != null) {
writer.close();
}
if (in != null) {
in.close();
}
} catch (IOException e1) {
LogManager.logStackTrace(e1);
}
}
}
public String getContents() {
if (instanceInstaller != null) {
if (instanceInstaller.isCancelled()) {
return null;
}
}
StringBuilder response = null;
try {
InputStream in = null;
if (isGziped() && App.useGzipForDownloads) {
in = new GZIPInputStream(getConnection().getInputStream());
} else {
in = getConnection().getInputStream();
}
BufferedReader br = new BufferedReader(new InputStreamReader(in));
response = new StringBuilder();
String inputLine;
while ((inputLine = br.readLine()) != null) {
response.append(inputLine);
}
in.close();
} catch (IOException e) {
LogManager.error("Failed to get contents of " + this.url + " due to IOException!");
LogManager.logStackTrace(e);
return null;
}
this.connection.disconnect();
return response.toString();
}
public void download(boolean downloadAsLibrary) {
download(downloadAsLibrary, false);
}
public void download(boolean downloadAsLibrary, boolean force) {
this.attempts = 0;
if (this.connection != null) {
this.connection.disconnect();
this.connection = null;
}
if (this.file == null) {
LogManager.error("Cannot download " + this.url + " to file as one wasn't specified!");
return;
}
if (this.file.exists()) {
this.oldFile = new File(this.file.getParent(), this.file.getName() + ".bak");
Utils.moveFile(this.file, this.oldFile, true);
}
if (instanceInstaller != null) {
if (instanceInstaller.isCancelled()) {
return;
}
}
if (!this.file.canWrite()) {
Utils.delete(this.file);
}
if (this.file.exists() && this.file.isFile()) {
Utils.delete(this.file);
}
// Create the directory structure
new File(this.file.getAbsolutePath().substring(0, this.file.getAbsolutePath().lastIndexOf(File.separatorChar)
)).mkdirs();
if (getHash().equalsIgnoreCase("-")) {
downloadFile(downloadAsLibrary); // Only download the file once since we have no MD5 to check
} else {
String fileHash = "0";
boolean done = false;
while (attempts <= 3) {
attempts++;
if (this.file.exists()) {
if (isMD5()) {
fileHash = Utils.getMD5(this.file);
} else {
fileHash = Utils.getSHA1(this.file);
}
} else {
fileHash = "0";
}
if (App.skipHashChecking || fileHash.equalsIgnoreCase(getHash())) {
done = true;
break; // Hash matches, file is good
}
if (this.connection != null) {
this.connection.disconnect();
this.connection = null;
}
if (this.file.exists()) {
Utils.delete(this.file); // Delete file since it doesn't match MD5
}
if (attempts != 1 && downloadAsLibrary) {
this.instanceInstaller.addTotalDownloadedBytes(this.size);
}
downloadFile(downloadAsLibrary); // Keep downloading file until it matches MD5
}
if (!done) {
if (this.isATLauncherDownload) {
if (getNextServer()) {
LogManager.warn("Error downloading " + this.file.getName() + " from " + this.url + ". " +
"Expected hash of " + getHash() + " but got " + fileHash + " instead. Trying another " +
"server!");
this.url = server.getFileURL(this.beforeURL);
if (downloadAsLibrary) {
this.instanceInstaller.addTotalDownloadedBytes(this.size);
}
download(downloadAsLibrary); // Redownload the file
} else {
Utils.copyFile(this.file, App.settings.getFailedDownloadsDir());
LogManager.error("Failed to download file " + this.file.getName() + " from all " + Constants.LAUNCHER_NAME +
"servers. Copied to FailedDownloads Folder. Cancelling install!");
if (this.instanceInstaller != null) {
instanceInstaller.cancel(true);
}
}
} else {
Utils.copyFile(this.file, App.settings.getFailedDownloadsDir());
LogManager.error("Error downloading " + this.file.getName() + " from " + this.url + ". Expected " +
"hash of " + getHash() + " but got " + fileHash + " instead. Copied to FailedDownloads " +
"Folder. Cancelling install!");
if (this.instanceInstaller != null) {
instanceInstaller.cancel(true);
}
}
} else if (this.copyTo != null && this.actuallyCopy) {
String fileHash2;
if (this.copyTo.exists()) {
if (isMD5()) {
fileHash2 = Utils.getMD5(this.file);
} else {
fileHash2 = Utils.getSHA1(this.file);
}
} else {
fileHash2 = "0";
}
if (!fileHash2.equalsIgnoreCase(getHash())) {
if (this.copyTo.exists()) {
Utils.delete(this.copyTo);
}
new File(this.copyTo.getAbsolutePath().substring(0, this.copyTo.getAbsolutePath().lastIndexOf
(File.separatorChar))).mkdirs();
Utils.copyFile(this.file, this.copyTo, true);
}
}
App.settings.clearTriedServers(); // Okay downloaded it so clear the servers used
}
if (this.oldFile != null && this.oldFile.exists()) {
Utils.delete(this.oldFile);
}
if (this.connection != null) {
this.connection.disconnect();
}
}
public boolean getNextServer() {
for (Server server : this.servers) {
if (this.server != server) {
LogManager.warn("Server " + this.server.getName() + " Not Available! Switching To " + server.getName());
this.servers.remove(this.server);
this.server = server; // Setup next available server
return true;
}
}
return false;
}
public int getResponseCode() {
try {
return getConnection().getResponseCode();
} catch (IOException e) {
LogManager.logStackTrace("IOException when getting response code for the url " + this.url, e);
return -1;
}
}
}