mirror of
https://github.com/SKCraft/Launcher.git
synced 2025-02-08 00:21:21 +01:00
Rewrote updating mechanism to be more reliable.
This commit is contained in:
parent
9843b5c883
commit
ae2a1f091f
28
src/main/java/com/skcraft/concurrency/DefaultProgress.java
Normal file
28
src/main/java/com/skcraft/concurrency/DefaultProgress.java
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.concurrency;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* A simple default implementation of {@link com.skcraft.concurrency.ProgressObservable}
|
||||
* with settable properties.
|
||||
*/
|
||||
@Data
|
||||
public class DefaultProgress implements ProgressObservable {
|
||||
|
||||
private String status;
|
||||
private double progress = -1;
|
||||
|
||||
public DefaultProgress() {
|
||||
}
|
||||
|
||||
public DefaultProgress(double progress, String status) {
|
||||
this.progress = progress;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
@ -65,14 +65,19 @@ public class ObservableFuture<V> implements ListenableFuture<V>, ProgressObserva
|
||||
return future.get(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return observable.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return observable.getProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return observable.toString();
|
||||
public String getStatus() {
|
||||
return observable.getStatus();
|
||||
}
|
||||
|
||||
}
|
||||
|
35
src/main/java/com/skcraft/concurrency/ProgressFilter.java
Normal file
35
src/main/java/com/skcraft/concurrency/ProgressFilter.java
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.concurrency;
|
||||
|
||||
public class ProgressFilter implements ProgressObservable {
|
||||
|
||||
private final ProgressObservable delegate;
|
||||
private final double offset;
|
||||
private final double portion;
|
||||
|
||||
public ProgressFilter(ProgressObservable delegate, double offset, double portion) {
|
||||
this.delegate = delegate;
|
||||
this.offset = offset;
|
||||
this.portion = portion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return offset + portion * Math.max(0, delegate.getProgress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return delegate.getStatus();
|
||||
}
|
||||
|
||||
public static ProgressObservable between(ProgressObservable delegate, double from, double to) {
|
||||
return new ProgressFilter(delegate, from, to - from);
|
||||
}
|
||||
|
||||
}
|
@ -20,4 +20,11 @@ public interface ProgressObservable {
|
||||
*/
|
||||
double getProgress();
|
||||
|
||||
/**
|
||||
* Get the current status text.
|
||||
*
|
||||
* @return the status text, or null if unavailable
|
||||
*/
|
||||
String getStatus();
|
||||
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ public class Instance implements Comparable<Instance> {
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public File getVersionManifestPath() {
|
||||
public File getVersionPath() {
|
||||
return new File(dir, "version.json");
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
package com.skcraft.launcher;
|
||||
|
||||
import com.skcraft.concurrency.DefaultProgress;
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
import com.skcraft.launcher.model.modpack.ManifestInfo;
|
||||
import com.skcraft.launcher.model.modpack.PackageList;
|
||||
@ -67,12 +68,15 @@ public class InstanceList {
|
||||
}
|
||||
|
||||
public final class Enumerator implements Callable<InstanceList>, ProgressObservable {
|
||||
private ProgressObservable progress = new DefaultProgress(-1, null);
|
||||
|
||||
private Enumerator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceList call() throws Exception {
|
||||
log.info("Enumerating instance list...");
|
||||
progress = new DefaultProgress(0, _("instanceLoader.loadingLocal"));
|
||||
|
||||
List<Instance> local = new ArrayList<Instance>();
|
||||
List<Instance> remote = new ArrayList<Instance>();
|
||||
@ -92,6 +96,8 @@ public class InstanceList {
|
||||
}
|
||||
}
|
||||
|
||||
progress = new DefaultProgress(0.3, _("instanceLoader.checkingRemote"));
|
||||
|
||||
try {
|
||||
URL packagesURL = launcher.getPackagesURL();
|
||||
|
||||
@ -166,7 +172,12 @@ public class InstanceList {
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return -1;
|
||||
return progress.getProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return progress.getStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -270,6 +270,48 @@ public final class Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to fetch a property.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the property
|
||||
*/
|
||||
public String prop(String key) {
|
||||
return getProperties().getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to fetch a property.
|
||||
*
|
||||
* @param key the key
|
||||
* @param args formatting arguments
|
||||
* @return the property
|
||||
*/
|
||||
public String prop(String key, String... args) {
|
||||
return String.format(getProperties().getProperty(key), (Object[]) args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to fetch a property.
|
||||
*
|
||||
* @param key the key
|
||||
* @return the property
|
||||
*/
|
||||
public URL propUrl(String key) {
|
||||
return HttpRequest.url(prop(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to fetch a property.
|
||||
*
|
||||
* @param key the key
|
||||
* @param args formatting arguments
|
||||
* @return the property
|
||||
*/
|
||||
public URL propUrl(String key, String... args) {
|
||||
return HttpRequest.url(prop(key, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap.
|
||||
*
|
||||
@ -317,4 +359,5 @@ public final class Launcher {
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ public class ClientFileCollector extends DirectoryWalker {
|
||||
task.setHash(hash);
|
||||
task.setLocation(hashedPath);
|
||||
task.setTo(relPath);
|
||||
task.setSize(file.length());
|
||||
destPath.getParentFile().mkdirs();
|
||||
ClientFileCollector.log.info(String.format("Adding %s from %s...", relPath, file.getAbsolutePath()));
|
||||
Files.copy(file, destPath);
|
||||
|
@ -17,12 +17,12 @@ import com.skcraft.launcher.auth.Session;
|
||||
import com.skcraft.launcher.launch.InstanceLauncher;
|
||||
import com.skcraft.launcher.launch.LaunchProcessHandler;
|
||||
import com.skcraft.launcher.persistence.Persistence;
|
||||
import com.skcraft.launcher.selfupdate.LauncherUpdateChecker;
|
||||
import com.skcraft.launcher.selfupdate.LauncherUpdater;
|
||||
import com.skcraft.launcher.selfupdate.UpdateChecker;
|
||||
import com.skcraft.launcher.selfupdate.SelfUpdater;
|
||||
import com.skcraft.launcher.swing.*;
|
||||
import com.skcraft.launcher.update.InstanceDeleter;
|
||||
import com.skcraft.launcher.update.InstanceResetter;
|
||||
import com.skcraft.launcher.update.InstanceUpdater;
|
||||
import com.skcraft.launcher.update.HardResetter;
|
||||
import com.skcraft.launcher.update.Remover;
|
||||
import com.skcraft.launcher.update.Updater;
|
||||
import com.skcraft.launcher.util.SwingExecutor;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
@ -167,7 +167,7 @@ public class LauncherFrame extends JFrame {
|
||||
}
|
||||
|
||||
private void checkLauncherUpdate() {
|
||||
ListenableFuture<URL> future = launcher.getExecutor().submit(new LauncherUpdateChecker(launcher));
|
||||
ListenableFuture<URL> future = launcher.getExecutor().submit(new UpdateChecker(launcher));
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<URL>() {
|
||||
@Override
|
||||
@ -187,7 +187,7 @@ public class LauncherFrame extends JFrame {
|
||||
private void selfUpdate() {
|
||||
URL url = updateUrl;
|
||||
if (url != null) {
|
||||
LauncherUpdater downloader = new LauncherUpdater(launcher, url);
|
||||
SelfUpdater downloader = new SelfUpdater(launcher, url);
|
||||
ObservableFuture<File> future = new ObservableFuture<File>(
|
||||
launcher.getExecutor().submit(downloader), downloader);
|
||||
|
||||
@ -333,7 +333,7 @@ public class LauncherFrame extends JFrame {
|
||||
}
|
||||
|
||||
// Execute the deleter
|
||||
InstanceDeleter resetter = new InstanceDeleter(instance);
|
||||
Remover resetter = new Remover(instance);
|
||||
ObservableFuture<Instance> future = new ObservableFuture<Instance>(
|
||||
launcher.getExecutor().submit(resetter), resetter);
|
||||
|
||||
@ -357,7 +357,7 @@ public class LauncherFrame extends JFrame {
|
||||
}
|
||||
|
||||
// Execute the resetter
|
||||
InstanceResetter resetter = new InstanceResetter(instance);
|
||||
HardResetter resetter = new HardResetter(instance);
|
||||
ObservableFuture<Instance> future = new ObservableFuture<Instance>(
|
||||
launcher.getExecutor().submit(resetter), resetter);
|
||||
|
||||
@ -424,7 +424,7 @@ public class LauncherFrame extends JFrame {
|
||||
|
||||
if (update) {
|
||||
// Execute the updater
|
||||
InstanceUpdater updater = new InstanceUpdater(launcher, instance);
|
||||
Updater updater = new Updater(launcher, instance);
|
||||
ObservableFuture<Instance> future = new ObservableFuture<Instance>(
|
||||
launcher.getExecutor().submit(updater), updater);
|
||||
|
||||
|
@ -347,6 +347,11 @@ public class LoginDialog extends JDialog {
|
||||
public double getProgress() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return _("login.loggingInStatus");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ import static com.skcraft.launcher.util.SharedLocale._;
|
||||
@Log
|
||||
public class ProgressDialog extends JDialog {
|
||||
|
||||
private final String defaultTitle;
|
||||
private final String defaultMessage;
|
||||
private final JLabel label = new JLabel();
|
||||
private final JPanel progressPanel = new JPanel(new BorderLayout(0, 5));
|
||||
private final JPanel textAreaPanel = new JPanel(new BorderLayout());
|
||||
@ -41,9 +43,12 @@ public class ProgressDialog extends JDialog {
|
||||
|
||||
public ProgressDialog(Window owner, String title, String message) {
|
||||
super(owner, title, ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
setResizable(false);
|
||||
initComponents();
|
||||
label.setText(message);
|
||||
defaultTitle = title;
|
||||
defaultMessage = message;
|
||||
setCompactSize();
|
||||
setLocationRelativeTo(owner);
|
||||
|
||||
@ -71,6 +76,9 @@ public class ProgressDialog extends JDialog {
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
progressBar.setMaximum(1000);
|
||||
progressBar.setMinimum(0);
|
||||
|
||||
buttonsPanel.addElement(detailsButton);
|
||||
buttonsPanel.addGlue();
|
||||
buttonsPanel.addElement(cancelButton);
|
||||
@ -174,8 +182,35 @@ public class ProgressDialog extends JDialog {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
dialog.logText.setText(String.valueOf(observable));
|
||||
dialog.logText.setCaretPosition(0);
|
||||
JProgressBar progressBar = dialog.progressBar;
|
||||
JTextArea logText = dialog.logText;
|
||||
JLabel label = dialog.label;
|
||||
|
||||
double progress = observable.getProgress();
|
||||
if (progress >= 0) {
|
||||
dialog.setTitle(_("progress.percentTitle",
|
||||
Math.round(progress * 100 * 100) / 100.0, dialog.defaultTitle));
|
||||
progressBar.setValue((int) (progress * 1000));
|
||||
progressBar.setIndeterminate(false);
|
||||
} else {
|
||||
dialog.setTitle( dialog.defaultTitle);
|
||||
progressBar.setIndeterminate(true);
|
||||
}
|
||||
|
||||
String status = observable.getStatus();
|
||||
if (status == null) {
|
||||
status = _("progress.defaultStatus");
|
||||
label.setText(dialog.defaultMessage);
|
||||
} else {
|
||||
int index = status.indexOf('\n');
|
||||
if (index == -1) {
|
||||
label.setText(status);
|
||||
} else {
|
||||
label.setText(status.substring(0, index));
|
||||
}
|
||||
}
|
||||
logText.setText(status);
|
||||
logText.setCaretPosition(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
21
src/main/java/com/skcraft/launcher/install/Downloader.java
Normal file
21
src/main/java/com/skcraft/launcher/install/Downloader.java
Normal file
@ -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.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.install;
|
||||
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface Downloader extends ProgressObservable {
|
||||
|
||||
File download(List<URL> urls, String key, long size, String name);
|
||||
|
||||
File download(URL url, String key, long size, String name);
|
||||
}
|
47
src/main/java/com/skcraft/launcher/install/FileCopy.java
Normal file
47
src/main/java/com/skcraft/launcher/install/FileCopy.java
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.io.Files;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
@Log
|
||||
public class FileCopy implements InstallTask {
|
||||
|
||||
private final File from;
|
||||
private final File to;
|
||||
|
||||
public FileCopy(@NonNull File from, @NonNull File to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
log.log(Level.INFO, "Copying to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()});
|
||||
to.getParentFile().mkdirs();
|
||||
Files.copy(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return _("installer.copyingFile", from, to);
|
||||
}
|
||||
|
||||
}
|
47
src/main/java/com/skcraft/launcher/install/FileMover.java
Normal file
47
src/main/java/com/skcraft/launcher/install/FileMover.java
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
@Log
|
||||
public class FileMover implements InstallTask {
|
||||
|
||||
private final File from;
|
||||
private final File to;
|
||||
|
||||
public FileMover(@NonNull File from, @NonNull File to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
log.log(Level.INFO, "Moving to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()});
|
||||
to.getParentFile().mkdirs();
|
||||
to.delete();
|
||||
from.renameTo(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return _("installer.movingFile", from, to);
|
||||
}
|
||||
|
||||
}
|
265
src/main/java/com/skcraft/launcher/install/HttpDownloader.java
Normal file
265
src/main/java/com/skcraft/launcher/install/HttpDownloader.java
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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 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 long downloaded = 0;
|
||||
private long total = 0;
|
||||
private int left = 0;
|
||||
private boolean hasError = false;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
throw new IOException("Some files 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() {
|
||||
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) + builder.toString();
|
||||
} else {
|
||||
return _("downloader.noDownloads");
|
||||
}
|
||||
}
|
||||
|
||||
public class HttpDownloadJob implements Runnable, ProgressObservable {
|
||||
private final File destFile;
|
||||
private final List<URL> urls;
|
||||
private final long size;
|
||||
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();
|
||||
} catch (IOException e) {
|
||||
hasError = true;
|
||||
} catch (InterruptedException e) {
|
||||
log.info("Download of " + destFile + " was interrupted");
|
||||
} finally {
|
||||
synchronized (HttpDownloader.this) {
|
||||
downloaded += size;
|
||||
running.remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void download() throws IOException, InterruptedException {
|
||||
log.log(Level.INFO, "Downloading {0} from {1}...", new Object[] { destFile, 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(retryDelay);
|
||||
}
|
||||
first = false;
|
||||
|
||||
try {
|
||||
request = HttpRequest.get(url);
|
||||
request.execute().expectResponseCode(200).saveContent(file);
|
||||
left--;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.update;
|
||||
package com.skcraft.launcher.install;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
@Log
|
||||
public class InstallLogFileMover implements InstallTask {
|
||||
|
||||
private final InstallLog installLog;
|
||||
private final File from;
|
||||
private final File to;
|
||||
|
||||
public InstallLogFileMover(InstallLog installLog, @NonNull File from, @NonNull File to) {
|
||||
this.installLog = installLog;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
InstallLogFileMover.log.log(Level.INFO, "Installing to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()});
|
||||
to.getParentFile().mkdirs();
|
||||
to.delete();
|
||||
from.renameTo(to);
|
||||
installLog.add(to, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return _("installer.movingFile", from, to);
|
||||
}
|
||||
|
||||
}
|
15
src/main/java/com/skcraft/launcher/install/InstallTask.java
Normal file
15
src/main/java/com/skcraft/launcher/install/InstallTask.java
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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.skcraft.concurrency.ProgressObservable;
|
||||
|
||||
public interface InstallTask extends ProgressObservable {
|
||||
|
||||
void execute() throws Exception;
|
||||
|
||||
}
|
85
src/main/java/com/skcraft/launcher/install/Installer.java
Normal file
85
src/main/java/com/skcraft/launcher/install/Installer.java
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.skcraft.concurrency.ProgressObservable;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
@Log
|
||||
public class Installer implements ProgressObservable {
|
||||
|
||||
@Getter private final File tempDir;
|
||||
private final HttpDownloader downloader;
|
||||
private InstallTask running;
|
||||
private int count = 0;
|
||||
private int finished = 0;
|
||||
|
||||
private List<InstallTask> queue = new ArrayList<InstallTask>();
|
||||
|
||||
public Installer(@NonNull File tempDir) {
|
||||
this.tempDir = tempDir;
|
||||
this.downloader = new HttpDownloader(tempDir);
|
||||
}
|
||||
|
||||
public synchronized void queue(@NonNull InstallTask runnable) {
|
||||
queue.add(runnable);
|
||||
count++;
|
||||
}
|
||||
|
||||
public void download() throws IOException, InterruptedException {
|
||||
downloader.execute();
|
||||
}
|
||||
|
||||
public synchronized void execute() throws Exception {
|
||||
queue = Collections.unmodifiableList(queue);
|
||||
|
||||
try {
|
||||
for (InstallTask runnable : queue) {
|
||||
checkInterrupted();
|
||||
running = runnable;
|
||||
runnable.execute();
|
||||
finished++;
|
||||
}
|
||||
} finally {
|
||||
running = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Downloader getDownloader() {
|
||||
return downloader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return finished / (double) count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
InstallTask running = this.running;
|
||||
if (running != null) {
|
||||
String status = running.getStatus();
|
||||
if (status == null) {
|
||||
status = running.toString();
|
||||
}
|
||||
return _("installer.executing", count - finished) + "\n" + status;
|
||||
} else {
|
||||
return _("installer.installing");
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.update;
|
||||
package com.skcraft.launcher.install;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
@ -4,7 +4,7 @@
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.update;
|
||||
package com.skcraft.launcher.install;
|
||||
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.common.io.Closer;
|
@ -10,16 +10,17 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.Files;
|
||||
import com.skcraft.concurrency.DefaultProgress;
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
import com.skcraft.launcher.AssetsRoot;
|
||||
import com.skcraft.launcher.Configuration;
|
||||
import com.skcraft.launcher.Instance;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.auth.Session;
|
||||
import com.skcraft.launcher.install.ZipExtract;
|
||||
import com.skcraft.launcher.model.minecraft.AssetsIndex;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.update.ZipExtract;
|
||||
import com.skcraft.launcher.util.Environment;
|
||||
import com.skcraft.launcher.util.Platform;
|
||||
import lombok.Getter;
|
||||
@ -36,6 +37,7 @@ import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
/**
|
||||
* Handles the launching of an instance.
|
||||
@ -43,12 +45,15 @@ import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
|
||||
@Log
|
||||
public class InstanceLauncher implements Callable<Process>, ProgressObservable {
|
||||
|
||||
private ProgressObservable progress = new DefaultProgress(0, _("instanceLauncher.preparing"));
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
private final Launcher launcher;
|
||||
private final Instance instance;
|
||||
private final Session session;
|
||||
private final File extractDir;
|
||||
@Getter @Setter private Environment environment = Environment.getInstance();
|
||||
|
||||
private VersionManifest versionManifest;
|
||||
private AssetsIndex assetsIndex;
|
||||
private File virtualAssetsDir;
|
||||
@ -64,10 +69,8 @@ public class InstanceLauncher implements Callable<Process>, ProgressObservable {
|
||||
* @param session the session
|
||||
* @param extractDir the directory to extract to
|
||||
*/
|
||||
public InstanceLauncher(@NonNull Launcher launcher,
|
||||
@NonNull Instance instance,
|
||||
@NonNull Session session,
|
||||
@NonNull File extractDir) {
|
||||
public InstanceLauncher(@NonNull Launcher launcher, @NonNull Instance instance,
|
||||
@NonNull Session session, @NonNull File extractDir) {
|
||||
this.launcher = launcher;
|
||||
this.instance = instance;
|
||||
this.session = session;
|
||||
@ -94,12 +97,15 @@ public class InstanceLauncher implements Callable<Process>, ProgressObservable {
|
||||
assetsRoot = launcher.getAssets();
|
||||
|
||||
// Load versionManifest and assets index
|
||||
versionManifest = mapper.readValue(instance.getVersionManifestPath(), VersionManifest.class);
|
||||
versionManifest = mapper.readValue(instance.getVersionPath(), VersionManifest.class);
|
||||
assetsIndex = mapper.readValue(assetsRoot.getIndexPath(versionManifest), AssetsIndex.class);
|
||||
|
||||
// Copy over assets to the tree
|
||||
progress = new DefaultProgress(0.1, _("instanceLauncher.preparingAssets"));
|
||||
virtualAssetsDir = assetsRoot.buildAssetTree(versionManifest);
|
||||
|
||||
progress = new DefaultProgress(0.9, _("instanceLauncher.collectingArgs"));
|
||||
|
||||
addJvmArgs();
|
||||
addLibraries();
|
||||
addJarArgs();
|
||||
@ -115,6 +121,8 @@ public class InstanceLauncher implements Callable<Process>, ProgressObservable {
|
||||
log.info("Launching: " + builder);
|
||||
checkInterrupted();
|
||||
|
||||
progress = new DefaultProgress(1, _("instanceLauncher.startingJava"));
|
||||
|
||||
return processBuilder.start();
|
||||
}
|
||||
|
||||
@ -301,4 +309,9 @@ public class InstanceLauncher implements Callable<Process>, ProgressObservable {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,15 +9,10 @@ package com.skcraft.launcher.model.minecraft;
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.LauncherUtils;
|
||||
import com.skcraft.launcher.util.Environment;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import com.skcraft.launcher.util.Platform;
|
||||
import lombok.Data;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
@ -127,21 +122,6 @@ public class Library {
|
||||
return path;
|
||||
}
|
||||
|
||||
public URL getURL(Launcher launcher, Environment environment, URL baseURL) {
|
||||
if (locallyAvailable && baseURL != null) {
|
||||
try {
|
||||
return LauncherUtils.concat(baseURL, getPath(environment));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(launcher.getProperties().getProperty("librariesUrl"));
|
||||
builder.append(getPath(environment));
|
||||
return HttpRequest.url(builder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Rule {
|
||||
private Action action;
|
||||
|
@ -7,30 +7,31 @@
|
||||
package com.skcraft.launcher.model.modpack;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.skcraft.launcher.update.FileDistribute;
|
||||
import com.skcraft.launcher.update.UpdateCache;
|
||||
import com.skcraft.launcher.install.InstallLog;
|
||||
import com.skcraft.launcher.install.InstallLogFileMover;
|
||||
import com.skcraft.launcher.install.Installer;
|
||||
import com.skcraft.launcher.install.UpdateCache;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.skcraft.launcher.LauncherUtils.concat;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class FileInstall extends Task {
|
||||
public class FileInstall extends ManifestEntry {
|
||||
|
||||
private String version;
|
||||
private String hash;
|
||||
private String location;
|
||||
private String to;
|
||||
private long size;
|
||||
|
||||
@JsonIgnore
|
||||
public String getImpliedVersion() {
|
||||
@ -43,31 +44,23 @@ public class FileInstall extends Task {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
UpdateCache updateCache = getInstaller().getUpdateCache();
|
||||
public void install(@NonNull Installer installer, @NonNull InstallLog log,
|
||||
@NonNull UpdateCache cache, @NonNull File contentDir) throws MalformedURLException {
|
||||
String targetPath = getTargetPath();
|
||||
URL url;
|
||||
File targetFile = new File(contentDir, targetPath);
|
||||
String fileVersion = getImpliedVersion();
|
||||
URL url = concat(getManifest().getObjectsUrl(), getLocation());
|
||||
|
||||
try {
|
||||
url = concat(getManifest().getObjectsURL(), getLocation());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException("Invalid URL encountered", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (updateCache.mark(FilenameUtils.normalize(targetPath), getImpliedVersion())) {
|
||||
File targetFile = new File(getInstaller().getDestinationDir(), targetPath);
|
||||
File sourceFile = getInstaller().download(url, getImpliedVersion());
|
||||
List<File> targets = new ArrayList<File>();
|
||||
targets.add(targetFile);
|
||||
getInstaller().submit(new FileDistribute(sourceFile, targets));
|
||||
if (cache.mark(FilenameUtils.normalize(targetPath), fileVersion)) {
|
||||
long size = this.size;
|
||||
if (size <= 0) {
|
||||
size = 10 * 1024;
|
||||
}
|
||||
|
||||
getInstaller().getCurrentLog().add(to, to);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to download " + url.toString(), e);
|
||||
File tempFile = installer.getDownloader().download(url, fileVersion, size, to);
|
||||
installer.queue(new InstallLogFileMover(log, tempFile, targetFile));
|
||||
} else {
|
||||
log.add(to, to);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import com.fasterxml.jackson.annotation.JsonManagedReference;
|
||||
import com.google.common.base.Strings;
|
||||
import com.skcraft.launcher.LauncherUtils;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.update.Installer;
|
||||
import com.skcraft.launcher.install.Installer;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
@ -31,13 +31,13 @@ public class Manifest extends BaseManifest {
|
||||
private String objectsLocation;
|
||||
private String gameVersion;
|
||||
@JsonManagedReference("manifest")
|
||||
private List<Task> tasks = new ArrayList<Task>();
|
||||
private List<ManifestEntry> tasks = new ArrayList<ManifestEntry>();
|
||||
@Getter @Setter @JsonIgnore
|
||||
private Installer installer;
|
||||
private VersionManifest versionManifest;
|
||||
|
||||
@JsonIgnore
|
||||
public URL getLibrariesURL() {
|
||||
public URL getLibrariesUrl() {
|
||||
if (Strings.nullToEmpty(getLibrariesLocation()) == null) {
|
||||
return baseUrl;
|
||||
}
|
||||
@ -50,7 +50,7 @@ public class Manifest extends BaseManifest {
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public URL getObjectsURL() {
|
||||
public URL getObjectsUrl() {
|
||||
if (Strings.nullToEmpty(getObjectsLocation()) == null) {
|
||||
return baseUrl;
|
||||
}
|
||||
|
@ -10,10 +10,14 @@ import com.fasterxml.jackson.annotation.JsonBackReference;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.skcraft.launcher.update.Installer;
|
||||
import com.skcraft.launcher.install.InstallLog;
|
||||
import com.skcraft.launcher.install.Installer;
|
||||
import com.skcraft.launcher.install.UpdateCache;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
@ -24,14 +28,11 @@ import lombok.ToString;
|
||||
})
|
||||
@Data
|
||||
@ToString(exclude = "manifest")
|
||||
public abstract class Task implements Runnable {
|
||||
public abstract class ManifestEntry {
|
||||
|
||||
@JsonBackReference("manifest")
|
||||
private Manifest manifest;
|
||||
|
||||
@JsonIgnore
|
||||
public Installer getInstaller() {
|
||||
return getManifest().getInstaller();
|
||||
}
|
||||
public abstract void install(Installer installer, InstallLog log, UpdateCache cache, File contentDir) throws Exception;
|
||||
|
||||
}
|
@ -29,6 +29,7 @@ import java.util.*;
|
||||
* @author <a href="mailto:hboutemy@apache.org">Herve Boutemy</a>
|
||||
* @version $Id$
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ComparableVersion
|
||||
implements Comparable {
|
||||
private String value;
|
||||
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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.selfupdate;
|
||||
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.update.FileDownloader;
|
||||
import com.skcraft.launcher.update.Installer;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class LauncherUpdater implements Callable<File>, ProgressObservable {
|
||||
|
||||
private final ListeningExecutorService executor =
|
||||
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
|
||||
private final Launcher launcher;
|
||||
private final URL url;
|
||||
private Installer currentInstaller;
|
||||
|
||||
public LauncherUpdater(@NonNull Launcher launcher, @NonNull URL url) {
|
||||
this.launcher = launcher;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File call() throws Exception {
|
||||
try {
|
||||
File dir = launcher.getLauncherBinariesDir();
|
||||
File finalPath = new File(dir, String.valueOf(System.currentTimeMillis()) + ".jar.pack");
|
||||
List<File> paths = new ArrayList<File>();
|
||||
paths.add(finalPath);
|
||||
|
||||
Installer installer = new Installer(executor, launcher.getInstallerDir(), dir, dir);
|
||||
currentInstaller = installer;
|
||||
installer.submit(new FileDownloader(installer, url, paths));
|
||||
installer.awaitCompletion();
|
||||
|
||||
return finalPath;
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Installer installer = currentInstaller;
|
||||
if (installer != null) {
|
||||
return installer.toString();
|
||||
} else {
|
||||
return "...";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.selfupdate;
|
||||
|
||||
import com.skcraft.concurrency.DefaultProgress;
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.install.FileMover;
|
||||
import com.skcraft.launcher.install.Installer;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
public class SelfUpdater implements Callable<File>, ProgressObservable {
|
||||
|
||||
private final Launcher launcher;
|
||||
private final URL url;
|
||||
private final Installer installer;
|
||||
private ProgressObservable progress = new DefaultProgress(0, _("updater.updating"));
|
||||
|
||||
public SelfUpdater(@NonNull Launcher launcher, @NonNull URL url) {
|
||||
this.launcher = launcher;
|
||||
this.url = url;
|
||||
this.installer = new Installer(launcher.getInstallerDir());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File call() throws Exception {
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
try {
|
||||
File dir = launcher.getLauncherBinariesDir();
|
||||
File file = new File(dir, String.valueOf(System.currentTimeMillis()) + ".jar.pack");
|
||||
File tempFile = installer.getDownloader().download(url, "", 10000, "launcher.jar.pack");
|
||||
|
||||
progress = installer.getDownloader();
|
||||
installer.download();
|
||||
|
||||
installer.queue(new FileMover(tempFile, file));
|
||||
|
||||
progress = installer;
|
||||
installer.execute();
|
||||
|
||||
return file;
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return progress.getProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return progress.getStatus();
|
||||
}
|
||||
|
||||
}
|
@ -18,18 +18,18 @@ import java.util.concurrent.Callable;
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
@Log
|
||||
public class LauncherUpdateChecker implements Callable<URL> {
|
||||
public class UpdateChecker implements Callable<URL> {
|
||||
|
||||
private final Launcher launcher;
|
||||
|
||||
public LauncherUpdateChecker(@NonNull Launcher launcher) {
|
||||
public UpdateChecker(@NonNull Launcher launcher) {
|
||||
this.launcher = launcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL call() throws Exception {
|
||||
try {
|
||||
LauncherUpdateChecker.log.info("Checking for update...");
|
||||
UpdateChecker.log.info("Checking for update...");
|
||||
|
||||
URL url = HttpRequest.url(launcher.getProperties().getProperty("selfUpdateUrl"));
|
||||
|
||||
@ -42,13 +42,13 @@ public class LauncherUpdateChecker implements Callable<URL> {
|
||||
ComparableVersion current = new ComparableVersion(launcher.getVersion());
|
||||
ComparableVersion latest = new ComparableVersion(versionInfo.getVersion());
|
||||
|
||||
LauncherUpdateChecker.log.info("Latest version is " + latest + ", while current is " + current);
|
||||
UpdateChecker.log.info("Latest version is " + latest + ", while current is " + current);
|
||||
|
||||
if (latest.compareTo(current) >= 1) {
|
||||
LauncherUpdateChecker.log.info("Update available at " + versionInfo.getUrl());
|
||||
UpdateChecker.log.info("Update available at " + versionInfo.getUrl());
|
||||
return versionInfo.getUrl();
|
||||
} else {
|
||||
LauncherUpdateChecker.log.info("No update required.");
|
||||
UpdateChecker.log.info("No update required.");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
198
src/main/java/com/skcraft/launcher/update/BaseUpdater.java
Normal file
198
src/main/java/com/skcraft/launcher/update/BaseUpdater.java
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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.update;
|
||||
|
||||
import com.skcraft.launcher.AssetsRoot;
|
||||
import com.skcraft.launcher.Instance;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.install.FileMover;
|
||||
import com.skcraft.launcher.install.InstallLog;
|
||||
import com.skcraft.launcher.install.Installer;
|
||||
import com.skcraft.launcher.install.UpdateCache;
|
||||
import com.skcraft.launcher.model.minecraft.Asset;
|
||||
import com.skcraft.launcher.model.minecraft.AssetsIndex;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import com.skcraft.launcher.model.modpack.ManifestEntry;
|
||||
import com.skcraft.launcher.persistence.Persistence;
|
||||
import com.skcraft.launcher.util.Environment;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
|
||||
import static com.skcraft.launcher.LauncherUtils.concat;
|
||||
|
||||
@Log
|
||||
public abstract class BaseUpdater {
|
||||
|
||||
private static final long JAR_SIZE_ESTIMATE = 5 * 1024 * 1024;
|
||||
private static final long LIBRARY_SIZE_ESTIMATE = 3 * 1024 * 1024;
|
||||
|
||||
private final Launcher launcher;
|
||||
private final Environment environment = Environment.getInstance();
|
||||
private final List<Runnable> executeOnCompletion = new ArrayList<Runnable>();
|
||||
|
||||
protected BaseUpdater(@NonNull Launcher launcher) {
|
||||
this.launcher = launcher;
|
||||
}
|
||||
|
||||
protected void complete() {
|
||||
for (Runnable runnable : executeOnCompletion) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
protected Manifest installPackage(@NonNull Installer installer, @NonNull Instance instance) throws Exception {
|
||||
final File contentDir = instance.getContentDir();
|
||||
final File logPath = new File(contentDir, "install_log.json");
|
||||
final File cachePath = new File(contentDir, "update_cache.json");
|
||||
|
||||
final InstallLog previousLog = Persistence.read(logPath, InstallLog.class);
|
||||
final InstallLog currentLog = new InstallLog();
|
||||
currentLog.setBaseDir(contentDir);
|
||||
final UpdateCache updateCache = Persistence.read(cachePath, UpdateCache.class);
|
||||
|
||||
Manifest manifest = HttpRequest
|
||||
.get(instance.getManifestURL())
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.saveContent(instance.getManifestPath())
|
||||
.asJson(Manifest.class);
|
||||
|
||||
if (manifest.getBaseUrl() == null) {
|
||||
manifest.setBaseUrl(instance.getManifestURL());
|
||||
}
|
||||
|
||||
for (ManifestEntry entry : manifest.getTasks()) {
|
||||
entry.install(installer, currentLog, updateCache, contentDir);
|
||||
}
|
||||
|
||||
executeOnCompletion.add(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Map.Entry<String, Set<String>> entry : previousLog.getEntrySet()) {
|
||||
for (String path : entry.getValue()) {
|
||||
if (!currentLog.has(path)) {
|
||||
new File(contentDir, path).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Persistence.write(logPath, currentLog);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.WARNING, "Failed to write install log", e);
|
||||
}
|
||||
|
||||
try {
|
||||
Persistence.write(cachePath, updateCache);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.WARNING, "Failed to write update cache", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
protected void installJar(@NonNull Installer installer,
|
||||
@NonNull File jarFile,
|
||||
@NonNull URL url) throws InterruptedException {
|
||||
// If the JAR does not exist, install it
|
||||
if (!jarFile.exists()) {
|
||||
List<File> targets = new ArrayList<File>();
|
||||
|
||||
File tempFile = installer.getDownloader().download(url, "", JAR_SIZE_ESTIMATE, jarFile.getName());
|
||||
installer.queue(new FileMover(tempFile, jarFile));
|
||||
log.info("Installing " + jarFile.getName() + " from " + url);
|
||||
}
|
||||
}
|
||||
|
||||
protected void installAssets(@NonNull Installer installer,
|
||||
@NonNull VersionManifest versionManifest,
|
||||
@NonNull URL indexUrl,
|
||||
@NonNull List<URL> sources) throws IOException, InterruptedException {
|
||||
AssetsRoot assetsRoot = launcher.getAssets();
|
||||
|
||||
AssetsIndex index = HttpRequest
|
||||
.get(indexUrl)
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.saveContent(assetsRoot.getIndexPath(versionManifest))
|
||||
.asJson(AssetsIndex.class);
|
||||
|
||||
for (Map.Entry<String, Asset> entry : index.getObjects().entrySet()) {
|
||||
checkInterrupted();
|
||||
|
||||
String hash = entry.getValue().getHash();
|
||||
String path = String.format("%s/%s", hash.subSequence(0, 2), hash);
|
||||
File targetFile = assetsRoot.getObjectPath(entry.getValue());
|
||||
|
||||
if (!targetFile.exists()) {
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
for (URL sourceUrl : sources) {
|
||||
try {
|
||||
urls.add(concat(sourceUrl, path));
|
||||
} catch (MalformedURLException e) {
|
||||
log.log(Level.WARNING, "Bad source URL for library: " + sourceUrl);
|
||||
}
|
||||
}
|
||||
|
||||
File tempFile = installer.getDownloader().download(
|
||||
sources, "", entry.getValue().getSize(), entry.getKey());
|
||||
installer.queue(new FileMover(tempFile, targetFile));
|
||||
log.info("Fetching " + path + " from " + urls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void installLibraries(@NonNull Installer installer,
|
||||
@NonNull VersionManifest versionManifest,
|
||||
@NonNull File librariesDir,
|
||||
@NonNull List<URL> sources) throws InterruptedException {
|
||||
|
||||
for (Library library : versionManifest.getLibraries()) {
|
||||
if (library.matches(environment)) {
|
||||
checkInterrupted();
|
||||
|
||||
String path = library.getPath(environment);
|
||||
File targetFile = new File(librariesDir, path);
|
||||
|
||||
if (!targetFile.exists()) {
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
for (URL sourceUrl : sources) {
|
||||
try {
|
||||
urls.add(concat(sourceUrl, path));
|
||||
} catch (MalformedURLException e) {
|
||||
log.log(Level.WARNING, "Bad source URL for library: " + sourceUrl);
|
||||
}
|
||||
}
|
||||
|
||||
File tempFile = installer.getDownloader().download(sources, "", LIBRARY_SIZE_ESTIMATE,
|
||||
library.getName() + ".jar");
|
||||
installer.queue(new FileMover( tempFile, targetFile));
|
||||
log.info("Fetching " + path + " from " + urls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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.update;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import lombok.Data;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@Data
|
||||
@Log
|
||||
public class FileCopy implements Runnable {
|
||||
|
||||
public static final Object driveAccessLock = new Object();
|
||||
|
||||
private final File from;
|
||||
private final File to;
|
||||
|
||||
public FileCopy(File from, File to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (driveAccessLock) {
|
||||
log.log(Level.INFO, "Copying to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()});
|
||||
try {
|
||||
to.getParentFile().mkdirs();
|
||||
Files.copy(from, to);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to copy to " + to, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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.update;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import lombok.Data;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@Data
|
||||
@Log
|
||||
public class FileDistribute implements Runnable {
|
||||
|
||||
private final File from;
|
||||
private final List<File> to;
|
||||
|
||||
public FileDistribute(File from, List<File> to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (FileCopy.driveAccessLock) {
|
||||
try {
|
||||
for (int i = 0; i < to.size(); i++) {
|
||||
File dest = to.get(i);
|
||||
dest.getParentFile().mkdirs();
|
||||
log.log(Level.INFO, "Copying to {0} (from {1})...",
|
||||
new Object[]{dest.getAbsoluteFile(), from.getName()});
|
||||
if (i == to.size() - 1) {
|
||||
dest.delete();
|
||||
from.renameTo(dest);
|
||||
} else {
|
||||
Files.copy(from, dest);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to copy to " + to, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* 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.update;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
@ToString(exclude = "installer")
|
||||
public class FileDownloader implements Runnable {
|
||||
|
||||
private final Installer installer;
|
||||
@Getter private final URL url;
|
||||
@Getter private final List<File> targets;
|
||||
|
||||
public FileDownloader(Installer installer, URL url, List<File> targets) {
|
||||
this.installer = installer;
|
||||
this.url = url;
|
||||
this.targets = targets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
File sourceFile = installer.download(url, "");
|
||||
installer.submit(new FileDistribute(sourceFile, targets));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to download " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* 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.update;
|
||||
|
||||
import com.skcraft.launcher.AssetsRoot;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.model.minecraft.Asset;
|
||||
import com.skcraft.launcher.model.minecraft.AssetsIndex;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.util.Environment;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
|
||||
import static com.skcraft.launcher.util.HttpRequest.url;
|
||||
|
||||
@Log
|
||||
public class GameUpdater implements Runnable {
|
||||
|
||||
private final Installer installer;
|
||||
private final Launcher launcher;
|
||||
private final VersionManifest versionManifest;
|
||||
private final URL librariesBaseURL;
|
||||
private Environment environment = Environment.getInstance();
|
||||
|
||||
public GameUpdater(@NonNull Installer installer,
|
||||
@NonNull Launcher launcher,
|
||||
@NonNull VersionManifest versionManifest, URL librariesBaseURL) {
|
||||
this.installer = installer;
|
||||
this.launcher = launcher;
|
||||
this.versionManifest = versionManifest;
|
||||
this.librariesBaseURL = librariesBaseURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
File librariesDir = launcher.getLibrariesDir();
|
||||
AssetsRoot assetsRoot = launcher.getAssets();
|
||||
File jarPath = launcher.getJarPath(versionManifest);
|
||||
|
||||
URL jarURL = url(String.format(
|
||||
launcher.getProperties().getProperty("jarUrl"), versionManifest.getId()));
|
||||
URL assetsIndexURL = url(String.format(
|
||||
launcher.getProperties().getProperty("assetsIndexUrl"), versionManifest.getAssetsIndex()));
|
||||
|
||||
// If the JAR does not exist, install it
|
||||
if (!jarPath.exists()) {
|
||||
List<File> targets = new ArrayList<File>();
|
||||
targets.add(jarPath);
|
||||
installer.submit(new FileDownloader(installer, jarURL, targets));
|
||||
}
|
||||
|
||||
// Install libraries
|
||||
for (Library library : versionManifest.getLibraries()) {
|
||||
if (library.matches(environment)) {
|
||||
URL url = library.getURL(launcher, environment, librariesBaseURL);
|
||||
File file = new File(librariesDir, library.getPath(environment));
|
||||
|
||||
if (!file.exists()) {
|
||||
List<File> targets = new ArrayList<File>();
|
||||
targets.add(file);
|
||||
installer.submit(new FileDownloader(installer, url, targets));
|
||||
}
|
||||
|
||||
checkInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
// Install assets
|
||||
AssetsIndex index = HttpRequest
|
||||
.get(assetsIndexURL)
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.saveContent(assetsRoot.getIndexPath(versionManifest))
|
||||
.asJson(AssetsIndex.class);
|
||||
|
||||
for (Map.Entry<String, Asset> entry : index.getObjects().entrySet()) {
|
||||
String hash = entry.getValue().getHash();
|
||||
URL url = url(String.format(
|
||||
launcher.getProperties().getProperty("assetUrl"), hash.subSequence(0, 2), hash));
|
||||
File path = assetsRoot.getObjectPath(entry.getValue());
|
||||
|
||||
checkInterrupted();
|
||||
|
||||
if (!path.exists()) {
|
||||
List<File> targets = new ArrayList<File>();
|
||||
targets.add(path);
|
||||
installer.submit(new FileDownloader(installer, url, targets));
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to get resources", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GameUpdater{" +
|
||||
"versionManifest.id=" + versionManifest.getId() +
|
||||
", environment=" + environment +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -16,12 +16,14 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class InstanceResetter implements Callable<Instance>, ProgressObservable {
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
public class HardResetter implements Callable<Instance>, ProgressObservable {
|
||||
|
||||
private final Instance instance;
|
||||
private File currentDir;
|
||||
|
||||
public InstanceResetter(@NonNull Instance instance) {
|
||||
public HardResetter(@NonNull Instance instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@ -30,6 +32,11 @@ public class InstanceResetter implements Callable<Instance>, ProgressObservable
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return _("instanceResetter.resetting", instance.getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instance call() throws Exception {
|
||||
instance.setInstalled(false);
|
@ -1,248 +0,0 @@
|
||||
/*
|
||||
* 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.update;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.hash.HashFunction;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import com.skcraft.launcher.persistence.Persistence;
|
||||
import lombok.Getter;
|
||||
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.logging.Level;
|
||||
|
||||
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
|
||||
import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
|
||||
|
||||
@Log
|
||||
public class Installer {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
private final HashFunction hf = Hashing.sha1();
|
||||
private final ListeningExecutorService executor;
|
||||
@Getter private final File temporaryDir;
|
||||
@Getter private final File dataFilesDir;
|
||||
@Getter private final File destinationDir;
|
||||
@Getter private final InstallLog currentLog = new InstallLog();
|
||||
@Getter private final InstallLog previousLog;
|
||||
@Getter private final UpdateCache updateCache;
|
||||
@Getter @Setter private int downloadTries = 5;
|
||||
@Getter @Setter private int tryDelay = 3000;
|
||||
private final List<HttpRequest> httpRequests = new ArrayList<HttpRequest>();
|
||||
private final Set<String> usedHashes = new HashSet<String>();
|
||||
private final File installLogPath;
|
||||
private final File updateCachePath;
|
||||
private final Set<Runnable> running = new HashSet<Runnable>();
|
||||
private final ErrorHandler errorHandler = new ErrorHandler();
|
||||
private Throwable throwable;
|
||||
|
||||
public Installer(ListeningExecutorService executor, File temporaryDir, File dataFilesDir, File destinationDir) {
|
||||
this.executor = executor;
|
||||
this.temporaryDir = temporaryDir;
|
||||
this.dataFilesDir = dataFilesDir;
|
||||
this.destinationDir = destinationDir;
|
||||
|
||||
installLogPath = new File(dataFilesDir, "install_log.json");
|
||||
updateCachePath = new File(dataFilesDir, "update_cache.json");
|
||||
|
||||
this.previousLog = Persistence.read(installLogPath, InstallLog.class);
|
||||
this.updateCache = Persistence.read(updateCachePath, UpdateCache.class);
|
||||
}
|
||||
|
||||
public File download(URL url, String version) throws IOException, InterruptedException {
|
||||
String baseId = hf.newHasher()
|
||||
.putString(url.toString(), Charsets.UTF_8)
|
||||
.putString(version, Charsets.UTF_8)
|
||||
.hash()
|
||||
.toString();
|
||||
String id = baseId;
|
||||
int index = 0;
|
||||
|
||||
while (usedHashes.contains(id)) {
|
||||
id = baseId + "_" + (index++);
|
||||
}
|
||||
usedHashes.add(id);
|
||||
|
||||
File dir = new File(temporaryDir, id.charAt(0) + File.separator + id.charAt(1));
|
||||
dir.mkdirs();
|
||||
File downloadPath = new File(dir, id + ".filepart");
|
||||
File tempPath = new File(dir, id + ".filedownload");
|
||||
|
||||
if (tempPath.exists()) {
|
||||
log.log(Level.INFO, "Using existing {0} for {1}...", new Object[]{tempPath, url});
|
||||
return tempPath;
|
||||
} else {
|
||||
log.log(Level.INFO, "Downloading {0} to {1}...", new Object[]{url, downloadPath});
|
||||
|
||||
int trial = 0;
|
||||
while (true) {
|
||||
HttpRequest request = HttpRequest.get(url);
|
||||
try {
|
||||
synchronized (this) {
|
||||
httpRequests.add(request);
|
||||
}
|
||||
request.execute()
|
||||
.expectResponseCode(200)
|
||||
.saveContent(downloadPath);
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
if (++trial >= downloadTries) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
log.log(Level.WARNING, String.format("Download of %s failed; retrying in %d ms", url, tryDelay), e);
|
||||
Thread.sleep(tryDelay);
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
httpRequests.remove(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadPath.renameTo(tempPath);
|
||||
return tempPath;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized ListenableFuture<?> submit(Runnable runnable) {
|
||||
running.add(runnable);
|
||||
ListenableFuture<?> future = executor.submit(runnable);
|
||||
Futures.addCallback(future, errorHandler);
|
||||
future.addListener(new RemoveRunnable(runnable), sameThreadExecutor());
|
||||
return future;
|
||||
}
|
||||
|
||||
public synchronized void submitAll(List<? extends Runnable> tasks) {
|
||||
for (Runnable runnable : tasks) {
|
||||
submit(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void failAll(Throwable throwable) {
|
||||
this.throwable = throwable;
|
||||
running.clear();
|
||||
executor.shutdownNow();
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public void awaitCompletion() throws ExecutionException, InterruptedException {
|
||||
try {
|
||||
synchronized (this) {
|
||||
while (running.size() > 0) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
|
||||
if (throwable != null) {
|
||||
throw new ExecutionException(throwable);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void commit() throws IOException, InterruptedException {
|
||||
deleteOldFiles();
|
||||
writeCache();
|
||||
}
|
||||
|
||||
protected void deleteOldFiles() throws InterruptedException {
|
||||
for (Map.Entry<String, Set<String>> entry : previousLog.getEntrySet()) {
|
||||
for (String path : entry.getValue()) {
|
||||
checkInterrupted();
|
||||
if (!currentLog.has(path)) {
|
||||
new File(getDestinationDir(), path).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeCache() throws IOException {
|
||||
Persistence.write(installLogPath, currentLog);
|
||||
Persistence.write(updateCachePath, updateCache);
|
||||
}
|
||||
|
||||
private class RemoveRunnable implements Runnable {
|
||||
private final Runnable runnable;
|
||||
|
||||
public RemoveRunnable(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (Installer.this) {
|
||||
running.remove(runnable);
|
||||
if (running.isEmpty()) {
|
||||
Installer.this.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ErrorHandler implements FutureCallback<Object> {
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
if (t instanceof InterruptedException) {
|
||||
return;
|
||||
}
|
||||
log.log(Level.WARNING, "Failed install stage", t);
|
||||
failAll(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (httpRequests.size() > 0) {
|
||||
builder.append("Downloads:\n");
|
||||
for (HttpRequest request : httpRequests) {
|
||||
builder.append("- ");
|
||||
builder.append(request.getUrl());
|
||||
builder.append(" (");
|
||||
double progress = request.getProgress();
|
||||
if (progress >= 0) {
|
||||
builder.append(Math.round(request.getProgress() * 100.0 * 100.0) / 100.0);
|
||||
builder.append("%)");
|
||||
} else {
|
||||
builder.append("pending)");
|
||||
}
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append("\n");
|
||||
|
||||
}
|
||||
|
||||
builder.append("Tasks:\n");
|
||||
for (Runnable runnable : running) {
|
||||
builder.append("- ");
|
||||
builder.append(runnable.toString());
|
||||
builder.append("\n");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
/*
|
||||
* 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.update;
|
||||
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
import com.skcraft.launcher.Instance;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import com.skcraft.launcher.persistence.Persistence;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.skcraft.launcher.util.HttpRequest.url;
|
||||
|
||||
@Log
|
||||
public class InstanceUpdater implements Callable<Instance>, ProgressObservable {
|
||||
|
||||
private final Launcher launcher;
|
||||
private final Instance instance;
|
||||
private final ListeningExecutorService executor =
|
||||
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(6));
|
||||
private Installer currentInstaller;
|
||||
|
||||
public InstanceUpdater(@NonNull Launcher launcher, @NonNull Instance instance) {
|
||||
this.launcher = launcher;
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
private URL getVersionManifestURL(String version) {
|
||||
return url(String.format(launcher.getProperties().getProperty("versionManifestUrl"), version));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instance call() throws Exception {
|
||||
try {
|
||||
log.info("Checking for an update for '" + instance.getName() + "'...");
|
||||
if (instance.getManifestURL() == null) {
|
||||
log.log(Level.INFO,
|
||||
"No URL set for {0}, so it can't be updated (the modpack may be removed from the server)",
|
||||
new Object[] { instance });
|
||||
} else if (instance.isUpdatePending() || !instance.isInstalled()) {
|
||||
log.log(Level.INFO, "Updating {0}...", new Object[]{instance});
|
||||
update(instance);
|
||||
} else {
|
||||
log.log(Level.INFO, "No update found for {0}.", new Object[] { instance });
|
||||
}
|
||||
|
||||
return instance;
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private void update(Instance instance) throws IOException, InterruptedException, ExecutionException {
|
||||
try {
|
||||
instance.setLocal(true);
|
||||
Persistence.commitAndForget(instance);
|
||||
|
||||
Installer installer = new Installer(executor,
|
||||
launcher.getInstallerDir(),
|
||||
instance.getDir(),
|
||||
instance.getContentDir());
|
||||
currentInstaller = installer;
|
||||
|
||||
Manifest manifest = HttpRequest
|
||||
.get(instance.getManifestURL())
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.saveContent(instance.getManifestPath())
|
||||
.asJson(Manifest.class);
|
||||
if (manifest.getBaseUrl() == null) {
|
||||
manifest.setBaseUrl(instance.getManifestURL());
|
||||
}
|
||||
manifest.setInstaller(installer);
|
||||
|
||||
installer.submitAll(manifest.getTasks());
|
||||
installer.awaitCompletion();
|
||||
installer.commit();
|
||||
|
||||
URL url = getVersionManifestURL(manifest.getGameVersion());
|
||||
log.log(Level.INFO, instance.getName() + ": Fetching version manifest from " + url + "...");
|
||||
|
||||
VersionManifest versionManifest = manifest.getVersionManifest();
|
||||
|
||||
if (versionManifest != null) {
|
||||
Persistence.write(instance.getVersionManifestPath(), versionManifest);
|
||||
} else {
|
||||
// The manifest doesn't come with its own version manifest, so let's download the one for the given
|
||||
// version of Minecraft
|
||||
versionManifest = HttpRequest
|
||||
.get(url)
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.saveContent(instance.getVersionManifestPath())
|
||||
.asJson(VersionManifest.class);
|
||||
}
|
||||
|
||||
installer = new Installer(executor,
|
||||
launcher.getInstallerDir(),
|
||||
launcher.getCommonDataDir(),
|
||||
launcher.getCommonDataDir());
|
||||
currentInstaller = installer;
|
||||
|
||||
log.log(Level.INFO, instance.getName() + ": Enumerating common data files...");
|
||||
installer.submit(new GameUpdater(installer, launcher, versionManifest, manifest.getLibrariesURL()));
|
||||
installer.awaitCompletion();
|
||||
|
||||
instance.setVersion(manifest.getVersion());
|
||||
instance.setUpdatePending(false);
|
||||
instance.setInstalled(true);
|
||||
instance.setLocal(true);
|
||||
Persistence.commitAndForget(instance);
|
||||
|
||||
log.log(Level.INFO, instance.getName() + " has been updated to version " + manifest.getVersion() + ".");
|
||||
} finally {
|
||||
currentInstaller = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Installer installer = currentInstaller;
|
||||
if (installer != null) {
|
||||
return installer.toString();
|
||||
} else {
|
||||
return "...";
|
||||
}
|
||||
}
|
||||
}
|
@ -16,12 +16,13 @@ import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
public class InstanceDeleter implements Callable<Instance>, ProgressObservable {
|
||||
public class Remover implements Callable<Instance>, ProgressObservable {
|
||||
|
||||
private final Instance instance;
|
||||
|
||||
public InstanceDeleter(@NonNull Instance instance) {
|
||||
public Remover(@NonNull Instance instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@ -30,6 +31,11 @@ public class InstanceDeleter implements Callable<Instance>, ProgressObservable {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return _("instanceDeleter.deleting", instance.getDir());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instance call() throws Exception {
|
||||
instance.setInstalled(false);
|
159
src/main/java/com/skcraft/launcher/update/Updater.java
Normal file
159
src/main/java/com/skcraft/launcher/update/Updater.java
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.update;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.skcraft.concurrency.DefaultProgress;
|
||||
import com.skcraft.concurrency.ProgressFilter;
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
import com.skcraft.launcher.Instance;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.install.Installer;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import com.skcraft.launcher.persistence.Persistence;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.skcraft.launcher.util.HttpRequest.url;
|
||||
import static com.skcraft.launcher.util.SharedLocale._;
|
||||
|
||||
@Log
|
||||
public class Updater extends BaseUpdater implements Callable<Instance>, ProgressObservable {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
private final Installer installer;
|
||||
private final Launcher launcher;
|
||||
private final Instance instance;
|
||||
|
||||
private List<URL> librarySources = new ArrayList<URL>();
|
||||
private List<URL> assetsSources = new ArrayList<URL>();
|
||||
|
||||
private ProgressObservable progress = new DefaultProgress(
|
||||
-1, _("instanceUpdater.preparingUpdate"));
|
||||
|
||||
public Updater(@NonNull Launcher launcher, @NonNull Instance instance) {
|
||||
super(launcher);
|
||||
|
||||
this.installer = new Installer(launcher.getInstallerDir());
|
||||
this.launcher = launcher;
|
||||
this.instance = instance;
|
||||
|
||||
librarySources.add(launcher.propUrl("librariesSource"));
|
||||
assetsSources.add(launcher.propUrl("assetsSource"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instance call() throws Exception {
|
||||
Updater.log.info("Checking for an update for '" + instance.getName() + "'...");
|
||||
if (instance.getManifestURL() == null) {
|
||||
Updater.log.log(Level.INFO,
|
||||
"No URL set for {0}, so it can't be updated (the modpack may be removed from the server)",
|
||||
new Object[] { instance });
|
||||
} else if (instance.isUpdatePending() || !instance.isInstalled()) {
|
||||
Updater.log.log(Level.INFO, "Updating {0}...", new Object[]{instance});
|
||||
update(instance);
|
||||
} else {
|
||||
Updater.log.log(Level.INFO, "No update found for {0}.", new Object[] { instance });
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private VersionManifest readVersionManifest(Manifest manifest) throws IOException, InterruptedException {
|
||||
// Check whether the package manifest contains an embedded version manifest,
|
||||
// otherwise we'll have to download the one for the given Minecraft version
|
||||
VersionManifest version = manifest.getVersionManifest();
|
||||
if (version != null) {
|
||||
mapper.writeValue(instance.getVersionPath(), version);
|
||||
return version;
|
||||
} else {
|
||||
URL url = url(String.format(
|
||||
launcher.getProperties().getProperty("versionManifestUrl"),
|
||||
manifest.getGameVersion()));
|
||||
|
||||
return HttpRequest
|
||||
.get(url)
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.saveContent(instance.getVersionPath())
|
||||
.asJson(VersionManifest.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given instance.
|
||||
*
|
||||
* @param instance the instance
|
||||
* @throws IOException thrown on I/O error
|
||||
* @throws InterruptedException thrown on interruption
|
||||
* @throws ExecutionException thrown on execution error
|
||||
*/
|
||||
protected void update(Instance instance) throws Exception {
|
||||
// Mark this instance as local
|
||||
instance.setLocal(true);
|
||||
Persistence.commitAndForget(instance);
|
||||
|
||||
// Install package and get the manifests
|
||||
progress = new DefaultProgress(-1, _("instanceUpdater.readingManifest"));
|
||||
Manifest manifest = installPackage(installer, instance);
|
||||
progress = new DefaultProgress(-1, _("instanceUpdater.readingVersion"));
|
||||
VersionManifest version = readVersionManifest(manifest);
|
||||
|
||||
progress = new DefaultProgress(-1, _("instanceUpdater.buildingDownloadList"));
|
||||
|
||||
// Install the .jar
|
||||
File jarPath = launcher.getJarPath(version);
|
||||
URL jarSource = launcher.propUrl("jarUrl", version.getId());
|
||||
installJar(installer, jarPath, jarSource);
|
||||
|
||||
// Download libraries and assets
|
||||
installLibraries(installer, version, launcher.getLibrariesDir(), librarySources);
|
||||
installAssets(installer, version, launcher.propUrl("assetsIndexUrl", version.getAssetsIndex()), assetsSources);
|
||||
|
||||
progress = ProgressFilter.between(installer.getDownloader(), 0, 0.9);
|
||||
installer.download();
|
||||
|
||||
progress = ProgressFilter.between(installer, 0.9, 1);
|
||||
installer.execute();
|
||||
|
||||
complete();
|
||||
|
||||
// Update the instance's information
|
||||
instance.setVersion(manifest.getVersion());
|
||||
instance.setUpdatePending(false);
|
||||
instance.setInstalled(true);
|
||||
instance.setLocal(true);
|
||||
Persistence.commitAndForget(instance);
|
||||
|
||||
Updater.log.log(Level.INFO, instance.getName() +
|
||||
" has been updated to version " + manifest.getVersion() + ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return progress.getProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return progress.getStatus();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -288,6 +288,11 @@ public class HttpRequest implements Closeable, ProgressObservable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (conn != null) conn.disconnect();
|
||||
|
@ -118,4 +118,32 @@ console.tray.forceClose=Force close...
|
||||
console.closeWindow=Close Window
|
||||
console.hideWindow=Hide Window
|
||||
console.confirmKill=Are sure that you wish to close the game forcefully? You may lose data.
|
||||
console.confirmKillTitle=Are you sure?
|
||||
console.confirmKillTitle=Are you sure?
|
||||
|
||||
downloader.downloadingList=Downloading {0} file(s)... ({1} remaining)
|
||||
downloader.jobProgress={1,number}%\t{0}
|
||||
downloader.jobPending=...\t{0}
|
||||
downloader.noDownloads=No pending downloads.
|
||||
|
||||
progress.defaultStatus=Working...
|
||||
progress.percentTitle=({0}%) {1}
|
||||
|
||||
installer.installing=Installing...
|
||||
installer.executing=Executing tasks... ({0} remaining)
|
||||
installer.copyingFile=Copying from {0} to {1}
|
||||
installer.movingFile=Moving {0} to {1}
|
||||
|
||||
updater.updating=Updating launcher...
|
||||
|
||||
instanceUpdater.preparingUpdate=Preparing to update...
|
||||
instanceUpdater.readingManifest=Reading package manifest...
|
||||
instanceUpdater.readingVersion=Reading version manifest...
|
||||
instanceUpdater.buildingDownloadList=Collecting files to download...
|
||||
instanceDeleter.deleting=Deleting {0}...
|
||||
instanceResetter.resetting=Resetting {0}...
|
||||
instanceLoader.loadingLocal=Loading local instances from disk...
|
||||
instanceLoader.checkingRemote=Checking for new modpacks...
|
||||
instanceLauncher.preparing=Preparing for launch...
|
||||
instanceLauncher.preparingAssets=Preparing assets for game...
|
||||
instanceLauncher.collectingArgs=Collecting arguments...
|
||||
instanceLauncher.startingJava=Starting java...
|
@ -9,10 +9,10 @@ agentName=Minecraft
|
||||
offlinePlayerName=Player
|
||||
|
||||
versionManifestUrl=https://s3.amazonaws.com/Minecraft.Download/versions/%1$s/%1$s.json
|
||||
librariesUrl=https://libraries.minecraft.net/
|
||||
librariesSource=https://libraries.minecraft.net/
|
||||
jarUrl=http://s3.amazonaws.com/Minecraft.Download/versions/%1$s/%1$s.jar
|
||||
assetsIndexUrl=https://s3.amazonaws.com/Minecraft.Download/indexes/%s.json
|
||||
assetUrl=http://resources.download.minecraft.net/%s/%s
|
||||
assetsSource=http://resources.download.minecraft.net/%s/%s
|
||||
yggdrasilAuthUrl=https://authserver.mojang.com/authenticate
|
||||
resetPasswordUrl=https://minecraft.net/resetpassword
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user