Add the base files to the Launcher Git.
This commit is contained in:
parent
d1b3d2168c
commit
7242015c76
376 changed files with 27499 additions and 2 deletions
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import com.skcraft.launcher.bootstrap.*;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileSystemView;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.skcraft.launcher.bootstrap.SharedLocale.tr;
|
||||
|
||||
@Log
|
||||
public class Bootstrap {
|
||||
|
||||
private static final int BOOTSTRAP_VERSION = 1;
|
||||
|
||||
@Getter private final File baseDir;
|
||||
@Getter private final boolean portable;
|
||||
@Getter private final File binariesDir;
|
||||
@Getter private final Properties properties;
|
||||
private final String[] originalArgs;
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
SimpleLogFormatter.configureGlobalLogger();
|
||||
SharedLocale.loadBundle("com.skcraft.launcher.lang.Bootstrap", Locale.getDefault());
|
||||
|
||||
boolean portable = isPortableMode();
|
||||
|
||||
Bootstrap bootstrap = new Bootstrap(portable, args);
|
||||
try {
|
||||
bootstrap.cleanup();
|
||||
bootstrap.launch();
|
||||
} catch (Throwable t) {
|
||||
Bootstrap.log.log(Level.WARNING, "Error", t);
|
||||
Bootstrap.setSwingLookAndFeel();
|
||||
SwingHelper.showErrorDialog(null, tr("errors.bootstrapError"), tr("errorTitle"), t);
|
||||
}
|
||||
}
|
||||
|
||||
public Bootstrap(boolean portable, String[] args) throws IOException {
|
||||
this.properties = BootstrapUtils.loadProperties(Bootstrap.class, "bootstrap.properties");
|
||||
|
||||
File baseDir = portable ? new File(".") : getUserLauncherDir();
|
||||
|
||||
this.baseDir = baseDir;
|
||||
this.portable = portable;
|
||||
this.binariesDir = new File(baseDir, "launcher");
|
||||
this.originalArgs = args;
|
||||
|
||||
binariesDir.mkdirs();
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
File[] files = binariesDir.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
return pathname.getName().endsWith(".tmp");
|
||||
}
|
||||
});
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void launch() throws Throwable {
|
||||
File[] files = binariesDir.listFiles(new LauncherBinary.Filter());
|
||||
List<LauncherBinary> binaries = new ArrayList<LauncherBinary>();
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
Bootstrap.log.info("Found " + file.getAbsolutePath() + "...");
|
||||
binaries.add(new LauncherBinary(file));
|
||||
}
|
||||
}
|
||||
|
||||
if (!binaries.isEmpty()) {
|
||||
launchExisting(binaries, true);
|
||||
} else {
|
||||
launchInitial();
|
||||
}
|
||||
}
|
||||
|
||||
public void launchInitial() throws Exception {
|
||||
Bootstrap.log.info("Downloading the launcher...");
|
||||
Thread thread = new Thread(new Downloader(this));
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void launchExisting(List<LauncherBinary> binaries, boolean redownload) throws Exception {
|
||||
Collections.sort(binaries);
|
||||
LauncherBinary working = null;
|
||||
Class<?> clazz = null;
|
||||
|
||||
for (LauncherBinary binary : binaries) {
|
||||
File testFile = binary.getPath();
|
||||
try {
|
||||
testFile = binary.getExecutableJar();
|
||||
Bootstrap.log.info("Trying " + testFile.getAbsolutePath() + "...");
|
||||
clazz = load(testFile);
|
||||
Bootstrap.log.info("Launcher loaded successfully.");
|
||||
working = binary;
|
||||
break;
|
||||
} catch (Throwable t) {
|
||||
Bootstrap.log.log(Level.WARNING, "Failed to load " + testFile.getAbsoluteFile(), t);
|
||||
}
|
||||
}
|
||||
|
||||
if (working != null) {
|
||||
for (LauncherBinary binary : binaries) {
|
||||
if (working != binary) {
|
||||
log.info("Removing " + binary.getPath() + "...");
|
||||
binary.remove();
|
||||
}
|
||||
}
|
||||
|
||||
execute(clazz);
|
||||
} else {
|
||||
if (redownload) {
|
||||
launchInitial();
|
||||
} else {
|
||||
throw new IOException("Failed to find launchable .jar");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void execute(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
|
||||
Method method = clazz.getDeclaredMethod("main", String[].class);
|
||||
String[] launcherArgs;
|
||||
|
||||
if (portable) {
|
||||
launcherArgs = new String[] {
|
||||
"--portable",
|
||||
"--dir",
|
||||
baseDir.getAbsolutePath(),
|
||||
"--bootstrap-version",
|
||||
String.valueOf(BOOTSTRAP_VERSION) };
|
||||
} else {
|
||||
launcherArgs = new String[] {
|
||||
"--dir",
|
||||
baseDir.getAbsolutePath(),
|
||||
"--bootstrap-version",
|
||||
String.valueOf(BOOTSTRAP_VERSION) };
|
||||
}
|
||||
|
||||
String[] args = new String[originalArgs.length + launcherArgs.length];
|
||||
System.arraycopy(launcherArgs, 0, args, 0, launcherArgs.length);
|
||||
System.arraycopy(originalArgs, 0, args, launcherArgs.length, originalArgs.length);
|
||||
|
||||
log.info("Launching with arguments " + Arrays.toString(args));
|
||||
|
||||
method.invoke(null, new Object[] { args });
|
||||
}
|
||||
|
||||
public Class<?> load(File jarFile) throws MalformedURLException, ClassNotFoundException {
|
||||
URL[] urls = new URL[] { jarFile.toURI().toURL() };
|
||||
URLClassLoader child = new URLClassLoader(urls, this.getClass().getClassLoader());
|
||||
Class<?> clazz = Class.forName(getProperties().getProperty("launcherClass"), true, child);
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public static void setSwingLookAndFeel() {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static File getFileChooseDefaultDir() {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
FileSystemView fsv = chooser.getFileSystemView();
|
||||
return fsv.getDefaultDirectory();
|
||||
}
|
||||
|
||||
private File getUserLauncherDir() {
|
||||
String osName = System.getProperty("os.name").toLowerCase();
|
||||
if (osName.contains("win")) {
|
||||
return new File(getFileChooseDefaultDir(), getProperties().getProperty("homeFolderWindows"));
|
||||
}
|
||||
|
||||
File dotFolder = new File(System.getProperty("user.home"), getProperties().getProperty("homeFolder"));
|
||||
String xdgFolderName = getProperties().getProperty("homeFolderLinux");
|
||||
|
||||
if (osName.contains("linux") && !dotFolder.exists() && xdgFolderName != null && !xdgFolderName.isEmpty()) {
|
||||
String xdgDataHome = System.getenv("XDG_DATA_HOME");
|
||||
if (xdgDataHome.isEmpty()) {
|
||||
xdgDataHome = System.getProperty("user.home") + "/.local/share";
|
||||
}
|
||||
|
||||
return new File(xdgDataHome, xdgFolderName);
|
||||
}
|
||||
|
||||
return dotFolder;
|
||||
}
|
||||
|
||||
private static boolean isPortableMode() {
|
||||
return new File("portable.txt").exists();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class BootstrapUtils {
|
||||
|
||||
private static final Pattern absoluteUrlPattern = Pattern.compile("^[A-Za-z0-9\\-]+://.*$");
|
||||
|
||||
private BootstrapUtils() {
|
||||
}
|
||||
|
||||
public static void checkInterrupted() throws InterruptedException {
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeQuietly(Closeable closeable) {
|
||||
try {
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public static Properties loadProperties(Class<?> clazz, String name) throws IOException {
|
||||
Properties prop = new Properties();
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = clazz.getResourceAsStream(name);
|
||||
prop.load(in);
|
||||
} finally {
|
||||
closeQuietly(in);
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import com.skcraft.launcher.Bootstrap;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import static com.skcraft.launcher.bootstrap.SharedLocale.tr;
|
||||
|
||||
@Log
|
||||
public class DownloadFrame extends JFrame {
|
||||
|
||||
private Downloader downloader;
|
||||
private Timer timer;
|
||||
|
||||
private final JLabel label = new JLabel();
|
||||
private final JPanel progressPanel = new JPanel(new BorderLayout(0, 5));
|
||||
private final JPanel textAreaPanel = new JPanel(new BorderLayout());
|
||||
private final JProgressBar progressBar = new JProgressBar();
|
||||
private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true);
|
||||
private final JButton cancelButton = new JButton(tr("button.cancal"));
|
||||
|
||||
public DownloadFrame(ProgressObservable observable) {
|
||||
super(tr("downloader.title"));
|
||||
setResizable(false);
|
||||
initComponents();
|
||||
label.setText(tr("downloader.pleaseWait"));
|
||||
setMinimumSize(new Dimension(400, 100));
|
||||
pack();
|
||||
setLocationRelativeTo(null);
|
||||
SwingHelper.setIconImage(this, Bootstrap.class, "bootstrapper_icon.png");
|
||||
|
||||
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent event) {
|
||||
if (confirmCancel()) {
|
||||
cancel();
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
buttonsPanel.addGlue();
|
||||
buttonsPanel.addElement(cancelButton);
|
||||
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(30, 13, 13, 13));;
|
||||
|
||||
progressBar.setIndeterminate(true);
|
||||
progressBar.setMinimum(0);
|
||||
progressBar.setMaximum(1000);
|
||||
progressBar.setPreferredSize(new Dimension(0, 16));
|
||||
|
||||
progressPanel.add(label, BorderLayout.NORTH);
|
||||
progressPanel.setBorder(BorderFactory.createEmptyBorder(13, 13, 0, 13));
|
||||
progressPanel.add(progressBar, BorderLayout.CENTER);
|
||||
textAreaPanel.setBorder(BorderFactory.createEmptyBorder(10, 13, 0, 13));
|
||||
|
||||
add(progressPanel, BorderLayout.NORTH);
|
||||
add(textAreaPanel, BorderLayout.CENTER);
|
||||
add(buttonsPanel, BorderLayout.SOUTH);
|
||||
|
||||
textAreaPanel.setVisible(false);
|
||||
cancelButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (confirmCancel()) {
|
||||
cancel();
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean confirmCancel() {
|
||||
return SwingHelper.confirmDialog(this, tr("progress.confirmCancel"), tr("progress.confirmCancelTitle"));
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
Downloader downloader = this.downloader;
|
||||
if (downloader != null) {
|
||||
downloader.cancel();
|
||||
} else {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setDownloader(Downloader downloader) {
|
||||
this.downloader = downloader;
|
||||
|
||||
if (downloader == null) {
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
} else {
|
||||
if (timer == null) {
|
||||
timer = new Timer();
|
||||
timer.scheduleAtFixedRate(new UpdateProgress( downloader), 500, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateProgress extends TimerTask {
|
||||
private final Downloader downloader;
|
||||
|
||||
public UpdateProgress(Downloader downloader) {
|
||||
this.downloader = downloader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Downloader downloader = DownloadFrame.this.downloader;
|
||||
if (downloader != null) {
|
||||
label.setText(downloader.getStatus());
|
||||
double progress = downloader.getProgress();
|
||||
if (progress < 0) {
|
||||
progressBar.setIndeterminate(true);
|
||||
} else {
|
||||
progressBar.setIndeterminate(false);
|
||||
progressBar.setValue((int) (1000 * progress));
|
||||
}
|
||||
} else {
|
||||
label.setText(tr("downloader.pleaseWait"));
|
||||
progressBar.setIndeterminate(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import com.skcraft.launcher.Bootstrap;
|
||||
import lombok.extern.java.Log;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.skcraft.launcher.bootstrap.BootstrapUtils.checkInterrupted;
|
||||
import static com.skcraft.launcher.bootstrap.SharedLocale.tr;
|
||||
|
||||
@Log
|
||||
public class Downloader implements Runnable, ProgressObservable {
|
||||
|
||||
private final Bootstrap bootstrap;
|
||||
private DownloadFrame dialog;
|
||||
private HttpRequest httpRequest;
|
||||
private Thread thread;
|
||||
|
||||
public Downloader(Bootstrap bootstrap) {
|
||||
this.bootstrap = bootstrap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.thread = Thread.currentThread();
|
||||
|
||||
try {
|
||||
execute();
|
||||
} catch (InterruptedException e) {
|
||||
log.log(Level.WARNING, "Interrupted");
|
||||
System.exit(0);
|
||||
} catch (Throwable t) {
|
||||
log.log(Level.WARNING, "Failed to download launcher", t);
|
||||
SwingHelper.showErrorDialog(null, tr("errors.failedDownloadError"), tr("errorTitle"), t);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void execute() throws Exception {
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bootstrap.setSwingLookAndFeel();
|
||||
dialog = new DownloadFrame(Downloader.this);
|
||||
dialog.setVisible(true);
|
||||
dialog.setDownloader(Downloader.this);
|
||||
}
|
||||
});
|
||||
|
||||
URL updateUrl = HttpRequest.url(bootstrap.getProperties().getProperty("latestUrl"));
|
||||
|
||||
log.info("Reading update URL " + updateUrl + "...");
|
||||
List<LauncherBinary> binaries = new ArrayList<LauncherBinary>();
|
||||
|
||||
try {
|
||||
String data = HttpRequest
|
||||
.get(updateUrl)
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.asString("UTF-8");
|
||||
|
||||
Object object = JSONValue.parse(data);
|
||||
URL url;
|
||||
|
||||
if (object instanceof JSONObject) {
|
||||
String rawUrl = String.valueOf(((JSONObject) object).get("url"));
|
||||
if (rawUrl != null) {
|
||||
url = HttpRequest.url(rawUrl.trim());
|
||||
} else {
|
||||
log.warning("Did not get valid update document - got:\n\n" + data);
|
||||
throw new IOException("Update URL did not return a valid result");
|
||||
}
|
||||
} else {
|
||||
log.warning("Did not get valid update document - got:\n\n" + data);
|
||||
throw new IOException("Update URL did not return a valid result");
|
||||
}
|
||||
|
||||
checkInterrupted();
|
||||
|
||||
File finalFile = new File(bootstrap.getBinariesDir(), System.currentTimeMillis() + ".jar");
|
||||
File tempFile = new File(finalFile.getParentFile(), finalFile.getName() + ".tmp");
|
||||
|
||||
log.info("Downloading " + url + " to " + tempFile.getAbsolutePath());
|
||||
|
||||
httpRequest = HttpRequest.get(url);
|
||||
httpRequest
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.saveContent(tempFile);
|
||||
|
||||
finalFile.delete();
|
||||
tempFile.renameTo(finalFile);
|
||||
|
||||
LauncherBinary binary = new LauncherBinary(finalFile);
|
||||
binaries.add(binary);
|
||||
} finally {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
dialog.setDownloader(null);
|
||||
dialog.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bootstrap.launchExisting(binaries, false);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
HttpRequest httpRequest = this.httpRequest;
|
||||
if (httpRequest != null) {
|
||||
double progress = httpRequest.getProgress();
|
||||
if (progress >= 0) {
|
||||
return String.format(tr("downloader.progressStatus"), progress * 100);
|
||||
}
|
||||
}
|
||||
|
||||
return tr("downloader.status");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
HttpRequest httpRequest = this.httpRequest;
|
||||
return httpRequest != null ? httpRequest.getProgress() : -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,504 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.skcraft.launcher.bootstrap.BootstrapUtils.checkInterrupted;
|
||||
import static com.skcraft.launcher.bootstrap.BootstrapUtils.closeQuietly;
|
||||
|
||||
/**
|
||||
* A simple fluent interface for performing HTTP requests that uses
|
||||
* {@link java.net.HttpURLConnection} or {@link javax.net.ssl.HttpsURLConnection}.
|
||||
*/
|
||||
@Log
|
||||
public class HttpRequest implements Closeable, ProgressObservable {
|
||||
|
||||
private static final int READ_TIMEOUT = 1000 * 60 * 10;
|
||||
private static final int READ_BUFFER_SIZE = 1024 * 8;
|
||||
|
||||
private final Map<String, String> headers = new HashMap<String, String>();
|
||||
private String method;
|
||||
@Getter
|
||||
private final URL url;
|
||||
private String contentType;
|
||||
private byte[] body;
|
||||
private HttpURLConnection conn;
|
||||
private InputStream inputStream;
|
||||
private int redirectCount;
|
||||
|
||||
private long contentLength = -1;
|
||||
private long readBytes = 0;
|
||||
|
||||
/**
|
||||
* Create a new HTTP request.
|
||||
*
|
||||
* @param method the method
|
||||
* @param url the URL
|
||||
*/
|
||||
private HttpRequest(String method, URL url) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit form data.
|
||||
*
|
||||
* @param form the form
|
||||
* @return this object
|
||||
*/
|
||||
public HttpRequest bodyForm(Form form) {
|
||||
contentType = "application/x-www-form-urlencoded";
|
||||
body = form.toString().getBytes();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header.
|
||||
*
|
||||
* @param key the header key
|
||||
* @param value the header value
|
||||
* @return this object
|
||||
*/
|
||||
public HttpRequest header(String key, String value) {
|
||||
headers.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the request.
|
||||
* <p/>
|
||||
* After execution, {@link #close()} should be called.
|
||||
*
|
||||
* @return this object
|
||||
* @throws java.io.IOException on I/O error
|
||||
*/
|
||||
public HttpRequest execute() throws IOException {
|
||||
boolean successful = false;
|
||||
|
||||
try {
|
||||
if (conn != null) {
|
||||
throw new IllegalArgumentException("Connection already executed");
|
||||
}
|
||||
|
||||
conn = this.runRequest(url);
|
||||
|
||||
inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ?
|
||||
conn.getInputStream() : conn.getErrorStream();
|
||||
|
||||
successful = true;
|
||||
} finally {
|
||||
if (!successful) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private HttpURLConnection runRequest(URL url) throws IOException {
|
||||
if (redirectCount > 20) {
|
||||
throw new IOException("Too many redirects!");
|
||||
}
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Java) SKMCLauncher");
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
|
||||
if (body != null) {
|
||||
conn.setRequestProperty("Content-Type", contentType);
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(body.length));
|
||||
conn.setDoInput(true);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
conn.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
conn.setRequestMethod(method);
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoOutput(true);
|
||||
conn.setReadTimeout(READ_TIMEOUT);
|
||||
|
||||
conn.connect();
|
||||
|
||||
if (body != null) {
|
||||
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
|
||||
out.write(body);
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
switch (conn.getResponseCode()) {
|
||||
case HttpURLConnection.HTTP_SEE_OTHER:
|
||||
method = "GET";
|
||||
body = null;
|
||||
case HttpURLConnection.HTTP_MOVED_PERM:
|
||||
case HttpURLConnection.HTTP_MOVED_TEMP:
|
||||
case HttpURLConnection.HTTP_ACCEPTED:
|
||||
case 307:
|
||||
case 308:
|
||||
String location = conn.getHeaderField("Location");
|
||||
redirectCount++;
|
||||
|
||||
return runRequest(new URL(this.url, location));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require that the response code is one of the given response codes.
|
||||
*
|
||||
* @param codes a list of codes
|
||||
* @return this object
|
||||
* @throws java.io.IOException if there is an I/O error or the response code is not expected
|
||||
*/
|
||||
public HttpRequest expectResponseCode(int... codes) throws IOException {
|
||||
int responseCode = getResponseCode();
|
||||
|
||||
for (int code : codes) {
|
||||
if (code == responseCode) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
close();
|
||||
throw new IOException("Did not get expected response code, got " + responseCode + " for " + url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response code.
|
||||
*
|
||||
* @return the response code
|
||||
* @throws java.io.IOException on I/O error
|
||||
*/
|
||||
public int getResponseCode() throws IOException {
|
||||
if (conn == null) {
|
||||
throw new IllegalArgumentException("No connection has been made");
|
||||
}
|
||||
|
||||
return conn.getResponseCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input stream.
|
||||
*
|
||||
* @return the input stream
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer the returned response.
|
||||
*
|
||||
* @return the buffered response
|
||||
* @throws java.io.IOException on I/O error
|
||||
* @throws InterruptedException on interruption
|
||||
*/
|
||||
public BufferedResponse returnContent() throws IOException, InterruptedException {
|
||||
if (inputStream == null) {
|
||||
throw new IllegalArgumentException("No input stream available");
|
||||
}
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
int b = 0;
|
||||
while ((b = inputStream.read()) != -1) {
|
||||
checkInterrupted();
|
||||
bos.write(b);
|
||||
}
|
||||
return new BufferedResponse(bos.toByteArray());
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the result to a file.
|
||||
*
|
||||
* @param file the file
|
||||
* @return this object
|
||||
* @throws java.io.IOException on I/O error
|
||||
* @throws InterruptedException on interruption
|
||||
*/
|
||||
public HttpRequest saveContent(File file) throws IOException, InterruptedException {
|
||||
FileOutputStream fos = null;
|
||||
BufferedOutputStream bos = null;
|
||||
|
||||
try {
|
||||
fos = new FileOutputStream(file);
|
||||
bos = new BufferedOutputStream(fos);
|
||||
|
||||
saveContent(bos);
|
||||
} finally {
|
||||
closeQuietly(bos);
|
||||
closeQuietly(fos);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the result to an output stream.
|
||||
*
|
||||
* @param out the output stream
|
||||
* @return this object
|
||||
* @throws java.io.IOException on I/O error
|
||||
* @throws InterruptedException on interruption
|
||||
*/
|
||||
public HttpRequest saveContent(OutputStream out) throws IOException, InterruptedException {
|
||||
BufferedInputStream bis;
|
||||
|
||||
try {
|
||||
String field = conn.getHeaderField("Content-Length");
|
||||
if (field != null) {
|
||||
long len = Long.parseLong(field);
|
||||
if (len >= 0) { // Let's just not deal with really big numbers
|
||||
contentLength = len;
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
bis = new BufferedInputStream(inputStream);
|
||||
|
||||
byte[] data = new byte[READ_BUFFER_SIZE];
|
||||
int len = 0;
|
||||
while ((len = bis.read(data, 0, READ_BUFFER_SIZE)) >= 0) {
|
||||
out.write(data, 0, len);
|
||||
readBytes += len;
|
||||
checkInterrupted();
|
||||
}
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
if (contentLength >= 0) {
|
||||
return readBytes / (double) contentLength;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (conn != null) conn.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a GET request.
|
||||
*
|
||||
* @param url the URL
|
||||
* @return a new request object
|
||||
*/
|
||||
public static HttpRequest get(URL url) {
|
||||
return request("GET", url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a POST request.
|
||||
*
|
||||
* @param url the URL
|
||||
* @return a new request object
|
||||
*/
|
||||
public static HttpRequest post(URL url) {
|
||||
return request("POST", url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a request.
|
||||
*
|
||||
* @param method the method
|
||||
* @param url the URL
|
||||
* @return a new request object
|
||||
*/
|
||||
public static HttpRequest request(String method, URL url) {
|
||||
return new HttpRequest(method, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link java.net.URL} and throw a {@link RuntimeException} if the URL
|
||||
* is not valid.
|
||||
*
|
||||
* @param url the url
|
||||
* @return a URL object
|
||||
* @throws RuntimeException if the URL is invalid
|
||||
*/
|
||||
public static URL url(String url) {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used with {@link #bodyForm(Form)}.
|
||||
*/
|
||||
public final static class Form {
|
||||
public final List<String> elements = new ArrayList<String>();
|
||||
|
||||
private Form() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key/value to the form.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return this object
|
||||
*/
|
||||
public Form add(String key, String value) {
|
||||
try {
|
||||
elements.add(URLEncoder.encode(key, "UTF-8") +
|
||||
"=" + URLEncoder.encode(value, "UTF-8"));
|
||||
return this;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (String element : elements) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
builder.append("&");
|
||||
}
|
||||
builder.append(element);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new form.
|
||||
*
|
||||
* @return a new form
|
||||
*/
|
||||
public static Form form() {
|
||||
return new Form();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to buffer the response in memory.
|
||||
*/
|
||||
public class BufferedResponse {
|
||||
private final byte[] data;
|
||||
|
||||
private BufferedResponse(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result as bytes.
|
||||
*
|
||||
* @return the data
|
||||
*/
|
||||
public byte[] asBytes() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result as a string.
|
||||
*
|
||||
* @param encoding the encoding
|
||||
* @return the string
|
||||
* @throws java.io.IOException on I/O error
|
||||
*/
|
||||
public String asString(String encoding) throws IOException {
|
||||
return new String(data, encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result as an instance of the given class that has been
|
||||
* deserialized from a XML payload.
|
||||
*
|
||||
* @return the object
|
||||
* @throws java.io.IOException on I/O error
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T asXml(Class<T> cls) throws IOException {
|
||||
try {
|
||||
JAXBContext context = JAXBContext.newInstance(cls);
|
||||
Unmarshaller um = context.createUnmarshaller();
|
||||
return (T) um.unmarshal(new ByteArrayInputStream(data));
|
||||
} catch (JAXBException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the result to a file.
|
||||
*
|
||||
* @param file the file
|
||||
* @return this object
|
||||
* @throws java.io.IOException on I/O error
|
||||
* @throws InterruptedException on interruption
|
||||
*/
|
||||
public BufferedResponse saveContent(File file) throws IOException, InterruptedException {
|
||||
FileOutputStream fos = null;
|
||||
BufferedOutputStream bos = null;
|
||||
|
||||
file.getParentFile().mkdirs();
|
||||
|
||||
try {
|
||||
fos = new FileOutputStream(file);
|
||||
bos = new BufferedOutputStream(fos);
|
||||
|
||||
saveContent(bos);
|
||||
} finally {
|
||||
closeQuietly(bos);
|
||||
closeQuietly(fos);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the result to an output stream.
|
||||
*
|
||||
* @param out the output stream
|
||||
* @return this object
|
||||
* @throws java.io.IOException on I/O error
|
||||
* @throws InterruptedException on interruption
|
||||
*/
|
||||
public BufferedResponse saveContent(OutputStream out) throws IOException, InterruptedException {
|
||||
out.write(data);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Log
|
||||
public class LauncherBinary implements Comparable<LauncherBinary> {
|
||||
|
||||
public static final Pattern PATTERN = Pattern.compile("^([0-9]+)\\.jar(\\.pack)?$");
|
||||
@Getter
|
||||
private final File path;
|
||||
private final long time;
|
||||
private final boolean packed;
|
||||
|
||||
public LauncherBinary(File path) {
|
||||
this.path = path;
|
||||
String name = path.getName();
|
||||
Matcher m = PATTERN.matcher(name);
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException("Invalid filename: " + path);
|
||||
}
|
||||
time = Long.parseLong(m.group(1));
|
||||
packed = m.group(2) != null;
|
||||
}
|
||||
|
||||
public File getExecutableJar() throws PackedJarException {
|
||||
if (packed) {
|
||||
log.warning("Launcher binary " + path.getAbsolutePath() + " is a pack200 file, which is " +
|
||||
"no longer supported.");
|
||||
|
||||
throw new PackedJarException("Cannot unpack .jar.pack files!");
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(LauncherBinary o) {
|
||||
if (time > o.time) {
|
||||
return -1;
|
||||
} else if (time < o.time) {
|
||||
return 1;
|
||||
} else {
|
||||
if (packed && !o.packed) {
|
||||
return 1;
|
||||
} else if (!packed && o.packed) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
path.delete();
|
||||
}
|
||||
|
||||
public static class Filter implements FileFilter {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return file.isFile() && LauncherBinary.PATTERN.matcher(file.getName()).matches();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PackedJarException extends Exception {
|
||||
public PackedJarException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class LinedBoxPanel extends JPanel {
|
||||
|
||||
@Getter
|
||||
private final boolean horizontal;
|
||||
@Getter @Setter
|
||||
private int spacing = 6;
|
||||
private boolean needsSpacer = false;
|
||||
|
||||
public LinedBoxPanel(boolean horizontal) {
|
||||
this.horizontal = horizontal;
|
||||
setLayout(new BoxLayout(this,
|
||||
horizontal ? BoxLayout.X_AXIS : BoxLayout.Y_AXIS));
|
||||
setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||
}
|
||||
|
||||
public LinedBoxPanel fullyPadded() {
|
||||
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addElement(Component component) {
|
||||
if (needsSpacer) {
|
||||
add(horizontal ?
|
||||
Box.createHorizontalStrut(spacing) :
|
||||
Box.createVerticalStrut(spacing));
|
||||
}
|
||||
add(component);
|
||||
needsSpacer = true;
|
||||
}
|
||||
|
||||
public void addGlue() {
|
||||
add(horizontal ?
|
||||
Box.createHorizontalGlue() :
|
||||
Box.createVerticalGlue());
|
||||
needsSpacer = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
/**
|
||||
* Implementations of this interface can provide information on the progress
|
||||
* of a task.
|
||||
*/
|
||||
public interface ProgressObservable {
|
||||
|
||||
/**
|
||||
* Get the progress value as a number between 0 and 1 (inclusive), or -1
|
||||
* if progress information is unavailable.
|
||||
*
|
||||
* @return the progress value
|
||||
*/
|
||||
double getProgress();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Handles loading a shared message {@link ResourceBundle}.
|
||||
*/
|
||||
@Log
|
||||
public class SharedLocale {
|
||||
|
||||
private static Locale locale = Locale.getDefault();
|
||||
private static ResourceBundle bundle;
|
||||
|
||||
/**
|
||||
* Get the current locale.
|
||||
*
|
||||
* @return the current locale
|
||||
*/
|
||||
public static Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current resource bundle.
|
||||
*
|
||||
* @return the current resource bundle, or null if not available
|
||||
*/
|
||||
public static ResourceBundle getBundle() {
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a string.
|
||||
*
|
||||
* <p>If the string is not available, then ${key} will be returned.</p>
|
||||
*
|
||||
* @param key the key
|
||||
* @return the translated string
|
||||
*/
|
||||
public static String tr(String key) {
|
||||
if (bundle != null) {
|
||||
try {
|
||||
return bundle.getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
log.log(Level.WARNING, "Failed to find message", e);
|
||||
}
|
||||
}
|
||||
|
||||
return "${" + key + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a translated string.
|
||||
*
|
||||
* <p>If the string is not available, then ${key}:args will be returned.</p>
|
||||
*
|
||||
* @param key the key
|
||||
* @param args arguments
|
||||
* @return a translated string
|
||||
*/
|
||||
public static String tr(String key, Object... args) {
|
||||
if (bundle != null) {
|
||||
try {
|
||||
MessageFormat formatter = new MessageFormat(tr(key));
|
||||
formatter.setLocale(getLocale());
|
||||
return formatter.format(args);
|
||||
} catch (MissingResourceException e) {
|
||||
log.log(Level.WARNING, "Failed to find message", e);
|
||||
}
|
||||
}
|
||||
|
||||
return "${" + key + "}:" + args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a shared resource bundle.
|
||||
*
|
||||
* @param baseName the bundle name
|
||||
* @param locale the locale
|
||||
* @return true if loaded successfully
|
||||
*/
|
||||
public static boolean loadBundle(@NonNull String baseName, @NonNull Locale locale) {
|
||||
try {
|
||||
SharedLocale.locale = locale;
|
||||
bundle = ResourceBundle.getBundle(baseName, locale,
|
||||
SharedLocale.class.getClassLoader());
|
||||
return true;
|
||||
} catch (MissingResourceException e) {
|
||||
log.log(Level.SEVERE, "Failed to load resource bundle", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.logging.*;
|
||||
|
||||
@Log
|
||||
public final class SimpleLogFormatter extends Formatter {
|
||||
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("[")
|
||||
.append(record.getLevel().getLocalizedName().toLowerCase())
|
||||
.append("] ")
|
||||
.append(formatMessage(record))
|
||||
.append(LINE_SEPARATOR);
|
||||
|
||||
if (record.getThrown() != null) {
|
||||
try {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
record.getThrown().printStackTrace(pw);
|
||||
pw.close();
|
||||
sb.append(sw.toString());
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void configureGlobalLogger() {
|
||||
Logger globalLogger = Logger.getLogger("");
|
||||
|
||||
// Set formatter
|
||||
for (Handler handler : globalLogger.getHandlers()) {
|
||||
handler.setFormatter(new SimpleLogFormatter());
|
||||
}
|
||||
|
||||
// Set level
|
||||
String logLevel = System.getProperty(
|
||||
SimpleLogFormatter.class.getCanonicalName() + ".logLevel", "INFO");
|
||||
try {
|
||||
Level level = Level.parse(logLevel);
|
||||
globalLogger.setLevel(level);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.log(Level.WARNING, "Invalid log level of " + logLevel, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* 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.bootstrap;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.skcraft.launcher.bootstrap.BootstrapUtils.closeQuietly;
|
||||
import static com.skcraft.launcher.bootstrap.SharedLocale.tr;
|
||||
|
||||
/**
|
||||
* Swing utility methods.
|
||||
*/
|
||||
@Log
|
||||
public final class SwingHelper {
|
||||
|
||||
private SwingHelper() {
|
||||
}
|
||||
|
||||
public static String htmlEscape(String str) {
|
||||
return str.replace(">", ">")
|
||||
.replace("<", "<")
|
||||
.replace("&", "&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an popup error dialog, with potential extra details shown either immediately
|
||||
* or available on the dialog.
|
||||
*
|
||||
* @param parentComponent the frame from which the dialog is displayed, otherwise
|
||||
* null to use the default frame
|
||||
* @param message the message to display
|
||||
* @param title the title string for the dialog
|
||||
* @see #showMessageDialog(java.awt.Component, String, String, String, int) for details
|
||||
*/
|
||||
public static void showErrorDialog(Component parentComponent, @NonNull String message,
|
||||
@NonNull String title) {
|
||||
showErrorDialog(parentComponent, message, title, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an popup error dialog, with potential extra details shown either immediately
|
||||
* or available on the dialog.
|
||||
*
|
||||
* @param parentComponent the frame from which the dialog is displayed, otherwise
|
||||
* null to use the default frame
|
||||
* @param message the message to display
|
||||
* @param title the title string for the dialog
|
||||
* @param throwable the exception, or null if there is no exception to show
|
||||
* @see #showMessageDialog(java.awt.Component, String, String, String, int) for details
|
||||
*/
|
||||
public static void showErrorDialog(Component parentComponent, @NonNull String message,
|
||||
@NonNull String title, Throwable throwable) {
|
||||
String detailsText = null;
|
||||
|
||||
// Get a string version of the exception and use that for
|
||||
// the extra details text
|
||||
if (throwable != null) {
|
||||
StringWriter sw = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(sw));
|
||||
detailsText = sw.toString();
|
||||
}
|
||||
|
||||
showMessageDialog(parentComponent,
|
||||
message, title,
|
||||
detailsText, JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a message dialog using
|
||||
* {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)}.
|
||||
*
|
||||
* <p>The dialog will be shown from the Event Dispatch Thread, regardless of the
|
||||
* thread it is called from. In either case, the method will block until the
|
||||
* user has closed the dialog (or dialog creation fails for whatever reason).</p>
|
||||
*
|
||||
* @param parentComponent the frame from which the dialog is displayed, otherwise
|
||||
* null to use the default frame
|
||||
* @param message the message to display
|
||||
* @param title the title string for the dialog
|
||||
* @param messageType see {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)}
|
||||
* for available message types
|
||||
*/
|
||||
public static void showMessageDialog(final Component parentComponent,
|
||||
@NonNull final String message,
|
||||
@NonNull final String title,
|
||||
final String detailsText,
|
||||
final int messageType) {
|
||||
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
// To force the label to wrap, convert the message to broken HTML
|
||||
String htmlMessage = "<html><div style=\"width: 250px\">" + htmlEscape(message);
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout(0, detailsText != null ? 20 : 0));
|
||||
|
||||
// Add the main message
|
||||
panel.add(new JLabel(htmlMessage), BorderLayout.NORTH);
|
||||
|
||||
// Add the extra details
|
||||
if (detailsText != null) {
|
||||
JTextArea textArea = new JTextArea(tr("errorDialog.reportError") + "\n\n" + detailsText);
|
||||
JLabel tempLabel = new JLabel();
|
||||
textArea.setFont(tempLabel.getFont());
|
||||
textArea.setBackground(tempLabel.getBackground());
|
||||
textArea.setTabSize(2);
|
||||
textArea.setEditable(false);;
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(textArea);
|
||||
scrollPane.setPreferredSize(new Dimension(350, 120));
|
||||
panel.add(scrollPane, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
JOptionPane.showMessageDialog(
|
||||
parentComponent, panel, title, messageType);
|
||||
} else {
|
||||
// Call method again from the Event Dispatch Thread
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
showMessageDialog(
|
||||
parentComponent, message, title,
|
||||
detailsText, messageType);
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the user a binary yes or no question.
|
||||
*
|
||||
* @param parentComponent the component
|
||||
* @param message the message to display
|
||||
* @param title the title string for the dialog
|
||||
* @return whether 'yes' was selected
|
||||
*/
|
||||
public static boolean confirmDialog(final Component parentComponent,
|
||||
@NonNull final String message,
|
||||
@NonNull final String title) {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
return JOptionPane.showConfirmDialog(
|
||||
parentComponent, message, title, JOptionPane.YES_NO_OPTION) ==
|
||||
JOptionPane.YES_OPTION;
|
||||
} else {
|
||||
// Use an AtomicBoolean to pass the result back from the
|
||||
// Event Dispatcher Thread
|
||||
final AtomicBoolean yesSelected = new AtomicBoolean();
|
||||
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
yesSelected.set(confirmDialog(parentComponent, title, message));
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return yesSelected.get();
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferedImage readIconImage(Class<?> clazz, String path) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = clazz.getResourceAsStream(path);
|
||||
if (in != null) {
|
||||
return ImageIO.read(in);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} finally {
|
||||
closeQuietly(in);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void setIconImage(Window frame, Class<?> clazz, String path) {
|
||||
BufferedImage image = readIconImage(clazz, path);
|
||||
if (image != null) {
|
||||
frame.setIconImage(image);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus a component.
|
||||
*
|
||||
* <p>The focus call happens in {@link javax.swing.SwingUtilities#invokeLater(Runnable)}.</p>
|
||||
*
|
||||
* @param component the component
|
||||
*/
|
||||
public static void focusLater(@NonNull final Component component) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (component instanceof JTextComponent) {
|
||||
((JTextComponent) component).selectAll();
|
||||
}
|
||||
component.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
#
|
||||
# SK's Minecraft Launcher
|
||||
# Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
# Please see LICENSE.txt for license information.
|
||||
#
|
||||
|
||||
homeFolderWindows=Example Launcher
|
||||
homeFolderLinux=example_launcher
|
||||
homeFolder=.examplelauncher
|
||||
launcherClass=com.skcraft.launcher.Launcher
|
||||
latestUrl=http://update.skcraft.com/quark/launcher/latest.json
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2 KiB |
|
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# SK's Minecraft Launcher
|
||||
# Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
# Please see LICENSE.txt for license information.
|
||||
#
|
||||
|
||||
button.cancal=Cancel
|
||||
|
||||
errorTitle=Error
|
||||
errors.failedDownloadError=The SKCraft launcher could not be downloaded.
|
||||
errors.bootstrapError=An error occurred while trying to bootstrap the launcher\!
|
||||
|
||||
errorDialog.reportError=To report this error, provide\:
|
||||
|
||||
downloader.title=Downloading SKCraft Launcher...
|
||||
downloader.pleaseWait=Please wait...
|
||||
downloader.status=Downloading latest SKCraft Launcher...
|
||||
downloader.progressStatus=Downloading latest SKCraft Launcher (%.2f%%)...
|
||||
|
||||
progress.confirmCancel=Are you sure that you wish to cancel?
|
||||
progress.confirmCancelTitle=Cancel
|
||||
Loading…
Add table
Add a link
Reference in a new issue