mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-10-26 02:34:31 +02:00
Rename .java to .kt
This commit is contained in:
251
src/main/kotlin/link/infra/packwiz/installer/DownloadTask.kt
Normal file
251
src/main/kotlin/link/infra/packwiz/installer/DownloadTask.kt
Normal file
@@ -0,0 +1,251 @@
|
||||
package link.infra.packwiz.installer;
|
||||
|
||||
import link.infra.packwiz.installer.metadata.IndexFile;
|
||||
import link.infra.packwiz.installer.metadata.ManifestFile;
|
||||
import link.infra.packwiz.installer.metadata.ModFile;
|
||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
||||
import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource;
|
||||
import link.infra.packwiz.installer.metadata.hash.Hash;
|
||||
import link.infra.packwiz.installer.metadata.hash.HashUtils;
|
||||
import link.infra.packwiz.installer.ui.IExceptionDetails;
|
||||
import link.infra.packwiz.installer.ui.IOptionDetails;
|
||||
import okio.Buffer;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
class DownloadTask implements IOptionDetails, IExceptionDetails {
|
||||
final IndexFile.File metadata;
|
||||
ManifestFile.File cachedFile = null;
|
||||
private Exception failure = null;
|
||||
private boolean alreadyUpToDate = false;
|
||||
private boolean metadataRequired = true;
|
||||
private boolean invalidated = false;
|
||||
// If file is new or isOptional changed to true, the option needs to be presented again
|
||||
private boolean newOptional = true;
|
||||
private final UpdateManager.Options.Side downloadSide;
|
||||
|
||||
private DownloadTask(IndexFile.File metadata, String defaultFormat, UpdateManager.Options.Side downloadSide) {
|
||||
this.metadata = metadata;
|
||||
if (metadata.getHashFormat() == null || metadata.getHashFormat().length() == 0) {
|
||||
metadata.setHashFormat(defaultFormat);
|
||||
}
|
||||
this.downloadSide = downloadSide;
|
||||
}
|
||||
|
||||
void invalidate() {
|
||||
invalidated = true;
|
||||
alreadyUpToDate = false;
|
||||
}
|
||||
|
||||
void updateFromCache(ManifestFile.File cachedFile) {
|
||||
if (failure != null) return;
|
||||
if (cachedFile == null) {
|
||||
this.cachedFile = new ManifestFile.File();
|
||||
return;
|
||||
}
|
||||
|
||||
this.cachedFile = cachedFile;
|
||||
|
||||
if (!invalidated) {
|
||||
Hash currHash;
|
||||
try {
|
||||
currHash = HashUtils.getHash(Objects.requireNonNull(metadata.getHashFormat()), Objects.requireNonNull(metadata.getHash()));
|
||||
} catch (Exception e) {
|
||||
failure = e;
|
||||
return;
|
||||
}
|
||||
if (currHash.equals(cachedFile.getHash())) {
|
||||
// Already up to date
|
||||
alreadyUpToDate = true;
|
||||
metadataRequired = false;
|
||||
}
|
||||
}
|
||||
if (cachedFile.isOptional()) {
|
||||
// Because option selection dialog might set this task to true/false, metadata is always needed to download
|
||||
// the file, and to show the description and name
|
||||
metadataRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
void downloadMetadata(IndexFile parentIndexFile, SpaceSafeURI indexUri) {
|
||||
if (failure != null) return;
|
||||
if (metadataRequired) {
|
||||
try {
|
||||
metadata.downloadMeta(parentIndexFile, indexUri);
|
||||
} catch (Exception e) {
|
||||
failure = e;
|
||||
return;
|
||||
}
|
||||
ModFile linkedFile = metadata.getLinkedFile();
|
||||
if (linkedFile != null) {
|
||||
ModFile.Option option = linkedFile.getOption();
|
||||
if (option != null) {
|
||||
if (option.getOptional()) {
|
||||
if (cachedFile.isOptional()) {
|
||||
// isOptional didn't change
|
||||
newOptional = false;
|
||||
} else {
|
||||
// isOptional false -> true, set option to it's default value
|
||||
// TODO: preserve previous option value, somehow??
|
||||
cachedFile.setOptionValue(option.getDefaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
cachedFile.setOptional(isOptional());
|
||||
cachedFile.setOnlyOtherSide(!correctSide());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void download(String packFolder, SpaceSafeURI indexUri) {
|
||||
if (failure != null) return;
|
||||
|
||||
// Ensure it is removed
|
||||
if (!cachedFile.getOptionValue() || !correctSide()) {
|
||||
if (cachedFile.getCachedLocation() == null) return;
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(packFolder, cachedFile.getCachedLocation()));
|
||||
} catch (IOException e) {
|
||||
// TODO: how much of a problem is this? use log4j/other log library to show warning?
|
||||
e.printStackTrace();
|
||||
}
|
||||
cachedFile.setCachedLocation(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (alreadyUpToDate) return;
|
||||
|
||||
Path destPath = Paths.get(packFolder, Objects.requireNonNull(metadata.getDestURI()).toString());
|
||||
|
||||
// Don't update files marked with preserve if they already exist on disk
|
||||
if (metadata.getPreserve()) {
|
||||
if (destPath.toFile().exists()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Hash hash;
|
||||
String fileHashFormat;
|
||||
ModFile linkedFile = metadata.getLinkedFile();
|
||||
if (linkedFile != null) {
|
||||
hash = linkedFile.getHash();
|
||||
fileHashFormat = Objects.requireNonNull(linkedFile.getDownload()).getHashFormat();
|
||||
} else {
|
||||
hash = metadata.getHashObj();
|
||||
fileHashFormat = metadata.getHashFormat();
|
||||
}
|
||||
|
||||
Source src = metadata.getSource(indexUri);
|
||||
assert fileHashFormat != null;
|
||||
GeneralHashingSource fileSource = HashUtils.getHasher(fileHashFormat).getHashingSource(src);
|
||||
Buffer data = new Buffer();
|
||||
Okio.buffer(fileSource).readAll(data);
|
||||
|
||||
if (fileSource.hashIsEqual(hash)) {
|
||||
Files.createDirectories(destPath.getParent());
|
||||
Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} else {
|
||||
// TODO: no more SYSOUT!!!!!!!!!
|
||||
System.out.println("Invalid hash for " + metadata.getDestURI().toString());
|
||||
System.out.println("Calculated: " + fileSource.getHash());
|
||||
System.out.println("Expected: " + hash);
|
||||
failure = new Exception("Hash invalid!");
|
||||
}
|
||||
|
||||
if (cachedFile.getCachedLocation() != null && !destPath.equals(Paths.get(packFolder, cachedFile.getCachedLocation()))) {
|
||||
// Delete old file if location changes
|
||||
Files.delete(Paths.get(packFolder, cachedFile.getCachedLocation()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failure = e;
|
||||
}
|
||||
if (failure == null) {
|
||||
if (cachedFile == null) {
|
||||
cachedFile = new ManifestFile.File();
|
||||
}
|
||||
// Update the manifest file
|
||||
try {
|
||||
cachedFile.setHash(metadata.getHashObj());
|
||||
} catch (Exception e) {
|
||||
failure = e;
|
||||
return;
|
||||
}
|
||||
cachedFile.setOptional(isOptional());
|
||||
cachedFile.setCachedLocation(metadata.getDestURI().toString());
|
||||
if (metadata.getLinkedFile() != null) {
|
||||
try {
|
||||
cachedFile.setLinkedFileHash(metadata.getLinkedFile().getHash());
|
||||
} catch (Exception e) {
|
||||
failure = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return failure;
|
||||
}
|
||||
|
||||
boolean isOptional() {
|
||||
if (metadata.getLinkedFile() != null) {
|
||||
return metadata.getLinkedFile().isOptional();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isNewOptional() {
|
||||
return isOptional() && this.newOptional;
|
||||
}
|
||||
|
||||
boolean correctSide() {
|
||||
if (metadata.getLinkedFile() != null && metadata.getLinkedFile().getSide() != null) {
|
||||
return metadata.getLinkedFile().getSide().hasSide(downloadSide);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return metadata.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getOptionValue() {
|
||||
return cachedFile.getOptionValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOptionDescription() {
|
||||
if (metadata.getLinkedFile() != null && metadata.getLinkedFile().getOption() != null) {
|
||||
return metadata.getLinkedFile().getOption().getDescription();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setOptionValue(boolean value) {
|
||||
if (value && !cachedFile.getOptionValue()) {
|
||||
// Ensure that an update is done if it changes from false to true, or from true to false
|
||||
alreadyUpToDate = false;
|
||||
}
|
||||
cachedFile.setOptionValue(value);
|
||||
}
|
||||
|
||||
static List<DownloadTask> createTasksFromIndex(IndexFile index, String defaultFormat, UpdateManager.Options.Side downloadSide) {
|
||||
ArrayList<DownloadTask> tasks = new ArrayList<>();
|
||||
for (IndexFile.File file : Objects.requireNonNull(index.getFiles())) {
|
||||
tasks.add(new DownloadTask(file, defaultFormat, downloadSide));
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
495
src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt
Normal file
495
src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt
Normal file
@@ -0,0 +1,495 @@
|
||||
package link.infra.packwiz.installer;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.moandjiezana.toml.Toml;
|
||||
import link.infra.packwiz.installer.metadata.IndexFile;
|
||||
import link.infra.packwiz.installer.metadata.ManifestFile;
|
||||
import link.infra.packwiz.installer.metadata.PackFile;
|
||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
||||
import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource;
|
||||
import link.infra.packwiz.installer.metadata.hash.Hash;
|
||||
import link.infra.packwiz.installer.metadata.hash.HashUtils;
|
||||
import link.infra.packwiz.installer.request.HandlerManager;
|
||||
import link.infra.packwiz.installer.ui.IExceptionDetails;
|
||||
import link.infra.packwiz.installer.ui.IUserInterface;
|
||||
import link.infra.packwiz.installer.ui.InputStateHandler;
|
||||
import link.infra.packwiz.installer.ui.InstallProgress;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class UpdateManager {
|
||||
|
||||
private final Options opts;
|
||||
public final IUserInterface ui;
|
||||
private boolean cancelled;
|
||||
private boolean cancelledStartGame = false;
|
||||
private InputStateHandler stateHandler;
|
||||
private boolean errorsOccurred = false;
|
||||
|
||||
public static class Options {
|
||||
SpaceSafeURI downloadURI = null;
|
||||
String manifestFile = "packwiz.json"; // TODO: make configurable
|
||||
String packFolder = ".";
|
||||
Side side = Side.CLIENT;
|
||||
|
||||
public enum Side {
|
||||
@SerializedName("client")
|
||||
CLIENT("client"),
|
||||
@SerializedName("server")
|
||||
SERVER("server"),
|
||||
@SerializedName("both")
|
||||
BOTH("both", new Side[] { CLIENT, SERVER });
|
||||
|
||||
private final String sideName;
|
||||
private final Side[] depSides;
|
||||
|
||||
Side(String sideName) {
|
||||
this.sideName = sideName.toLowerCase();
|
||||
this.depSides = null;
|
||||
}
|
||||
|
||||
Side(String sideName, Side[] depSides) {
|
||||
this.sideName = sideName.toLowerCase();
|
||||
this.depSides = depSides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.sideName;
|
||||
}
|
||||
|
||||
public boolean hasSide(Side tSide) {
|
||||
if (this.equals(tSide)) {
|
||||
return true;
|
||||
}
|
||||
if (this.depSides != null) {
|
||||
for (Side depSide : this.depSides) {
|
||||
if (depSide.equals(tSide)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Side from(String name) {
|
||||
String lowerName = name.toLowerCase();
|
||||
for (Side side : Side.values()) {
|
||||
if (side.sideName.equals(lowerName)) {
|
||||
return side;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateManager(Options opts, IUserInterface ui, InputStateHandler inputStateHandler) {
|
||||
this.opts = opts;
|
||||
this.ui = ui;
|
||||
this.stateHandler = inputStateHandler;
|
||||
this.start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
this.checkOptions();
|
||||
|
||||
ui.submitProgress(new InstallProgress("Loading manifest file..."));
|
||||
Gson gson = new GsonBuilder().registerTypeAdapter(Hash.class, new Hash.TypeHandler()).create();
|
||||
ManifestFile manifest;
|
||||
try {
|
||||
manifest = gson.fromJson(new FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()),
|
||||
ManifestFile.class);
|
||||
} catch (FileNotFoundException e) {
|
||||
manifest = new ManifestFile();
|
||||
} catch (JsonSyntaxException | JsonIOException e) {
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
handleCancellation();
|
||||
}
|
||||
|
||||
ui.submitProgress(new InstallProgress("Loading pack file..."));
|
||||
GeneralHashingSource packFileSource;
|
||||
try {
|
||||
Source src = HandlerManager.getFileSource(opts.downloadURI);
|
||||
packFileSource = HashUtils.getHasher("sha256").getHashingSource(src);
|
||||
} catch (Exception e) {
|
||||
// TODO: still launch the game if updating doesn't work?
|
||||
// TODO: ask user if they want to launch the game, exit(1) if they don't
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
PackFile pf;
|
||||
try {
|
||||
pf = new Toml().read(Okio.buffer(packFileSource).inputStream()).to(PackFile.class);
|
||||
} catch (IllegalStateException e) {
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
handleCancellation();
|
||||
}
|
||||
|
||||
ui.submitProgress(new InstallProgress("Checking local files..."));
|
||||
|
||||
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked
|
||||
List<SpaceSafeURI> invalidatedUris = new ArrayList<>();
|
||||
if (manifest.getCachedFiles() != null) {
|
||||
for (Map.Entry<SpaceSafeURI, ManifestFile.File> entry : manifest.getCachedFiles().entrySet()) {
|
||||
// ignore onlyOtherSide files
|
||||
if (entry.getValue().getOnlyOtherSide()) {
|
||||
continue;
|
||||
}
|
||||
boolean invalid = false;
|
||||
// if isn't optional, or is optional but optionValue == true
|
||||
if (!entry.getValue().isOptional() || entry.getValue().getOptionValue()) {
|
||||
if (entry.getValue().getCachedLocation() != null) {
|
||||
if (!Paths.get(opts.packFolder, entry.getValue().getCachedLocation()).toFile().exists()) {
|
||||
invalid = true;
|
||||
}
|
||||
} else {
|
||||
// if cachedLocation == null, should probably be installed!!
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
if (invalid) {
|
||||
SpaceSafeURI fileUri = entry.getKey();
|
||||
System.out.println("File " + fileUri.toString() + " invalidated, marked for redownloading");
|
||||
invalidatedUris.add(fileUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.getPackFileHash() != null && packFileSource.hashIsEqual(manifest.getPackFileHash()) && invalidatedUris.isEmpty()) {
|
||||
System.out.println("Modpack is already up to date!");
|
||||
// todo: --force?
|
||||
if (!stateHandler.getOptionsButton()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Modpack name: " + pf.getName());
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
handleCancellation();
|
||||
}
|
||||
|
||||
try {
|
||||
// This is badly written, I'll probably heavily refactor it at some point
|
||||
processIndex(HandlerManager.getNewLoc(opts.downloadURI, Objects.requireNonNull(pf.getIndex()).getFile()),
|
||||
HashUtils.getHash(Objects.requireNonNull(pf.getIndex().getHashFormat()), Objects.requireNonNull(pf.getIndex().getHash())), pf.getIndex().getHashFormat(), manifest, invalidatedUris);
|
||||
} catch (Exception e1) {
|
||||
ui.handleExceptionAndExit(e1);
|
||||
}
|
||||
|
||||
handleCancellation();
|
||||
|
||||
// TODO: update MMC params, java args etc
|
||||
|
||||
// If there were errors, don't write the manifest/index hashes, to ensure they are rechecked later
|
||||
if (errorsOccurred) {
|
||||
manifest.setIndexFileHash(null);
|
||||
manifest.setPackFileHash(null);
|
||||
} else {
|
||||
manifest.setPackFileHash(packFileSource.getHash());
|
||||
}
|
||||
|
||||
manifest.setCachedSide(opts.side);
|
||||
try (Writer writer = new FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString())) {
|
||||
gson.toJson(manifest, writer);
|
||||
} catch (IOException e) {
|
||||
// TODO: add message?
|
||||
ui.handleException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void checkOptions() {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
private void processIndex(SpaceSafeURI indexUri, Hash indexHash, String hashFormat, ManifestFile manifest, List<SpaceSafeURI> invalidatedUris) {
|
||||
if (manifest.getIndexFileHash() != null && manifest.getIndexFileHash().equals(indexHash) && invalidatedUris.isEmpty()) {
|
||||
System.out.println("Modpack files are already up to date!");
|
||||
if (!stateHandler.getOptionsButton()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
manifest.setIndexFileHash(indexHash);
|
||||
|
||||
GeneralHashingSource indexFileSource;
|
||||
try {
|
||||
Source src = HandlerManager.getFileSource(indexUri);
|
||||
indexFileSource = HashUtils.getHasher(hashFormat).getHashingSource(src);
|
||||
} catch (Exception e) {
|
||||
// TODO: still launch the game if updating doesn't work?
|
||||
// TODO: ask user if they want to launch the game, exit(1) if they don't
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
IndexFile indexFile;
|
||||
try {
|
||||
indexFile = new Toml().read(Okio.buffer(indexFileSource).inputStream()).to(IndexFile.class);
|
||||
} catch (IllegalStateException e) {
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!indexFileSource.hashIsEqual(indexHash)) {
|
||||
// TODO: throw exception
|
||||
System.out.println("I was meant to put an error message here but I'll do that later");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
ui.submitProgress(new InstallProgress("Checking local files..."));
|
||||
Iterator<Map.Entry<SpaceSafeURI, ManifestFile.File>> it = manifest.getCachedFiles().entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<SpaceSafeURI, ManifestFile.File> entry = it.next();
|
||||
if (entry.getValue().getCachedLocation() != null) {
|
||||
boolean alreadyDeleted = false;
|
||||
// Delete if option value has been set to false
|
||||
if (entry.getValue().isOptional() && !entry.getValue().getOptionValue()) {
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().getCachedLocation()));
|
||||
} catch (IOException e) {
|
||||
// TODO: should this be shown to the user in some way?
|
||||
e.printStackTrace();
|
||||
}
|
||||
// Set to null, as it doesn't exist anymore
|
||||
entry.getValue().setCachedLocation(null);
|
||||
alreadyDeleted = true;
|
||||
}
|
||||
if (indexFile.getFiles().stream().noneMatch(f -> Objects.equals(f.getFile(), entry.getKey()))) {
|
||||
// File has been removed from the index
|
||||
if (!alreadyDeleted) {
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().getCachedLocation()));
|
||||
} catch (IOException e) {
|
||||
// TODO: should this be shown to the user in some way?
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
return;
|
||||
}
|
||||
ui.submitProgress(new InstallProgress("Comparing new files..."));
|
||||
|
||||
// TODO: progress bar?
|
||||
if (indexFile.getFiles().isEmpty()) {
|
||||
System.out.println("Warning: Index is empty!");
|
||||
}
|
||||
List<DownloadTask> tasks = DownloadTask.createTasksFromIndex(indexFile, indexFile.getHashFormat(), opts.side);
|
||||
// If the side changes, invalidate EVERYTHING just in case
|
||||
// Might not be needed, but done just to be safe
|
||||
boolean invalidateAll = !opts.side.equals(manifest.getCachedSide());
|
||||
if (invalidateAll) {
|
||||
System.out.println("Side changed, invalidating all mods");
|
||||
}
|
||||
tasks.forEach(f -> {
|
||||
// TODO: should linkedfile be checked as well? should this be done in the download section?
|
||||
if (invalidateAll) {
|
||||
f.invalidate();
|
||||
} else if (invalidatedUris.contains(f.metadata.getFile())) {
|
||||
f.invalidate();
|
||||
}
|
||||
ManifestFile.File file = manifest.getCachedFiles().get(f.metadata.getFile());
|
||||
if (file != null) {
|
||||
// Ensure the file can be reverted later if necessary - the DownloadTask modifies the file so if it fails we need the old version back
|
||||
file.backup();
|
||||
}
|
||||
// If it is null, the DownloadTask will make a new empty cachedFile
|
||||
f.updateFromCache(file);
|
||||
});
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's hope downloadMetadata is a pure function!!!
|
||||
tasks.parallelStream().forEach(f -> f.downloadMetadata(indexFile, indexUri));
|
||||
|
||||
List<IExceptionDetails> failedTasks = tasks.stream().filter(t -> t.getException() != null).collect(Collectors.toList());
|
||||
if (!failedTasks.isEmpty()) {
|
||||
errorsOccurred = true;
|
||||
IExceptionDetails.ExceptionListResult exceptionListResult;
|
||||
try {
|
||||
exceptionListResult = ui.showExceptions(failedTasks, tasks.size(), true).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
// Interrupted means cancelled???
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
switch (exceptionListResult) {
|
||||
case CONTINUE:
|
||||
break;
|
||||
case CANCEL:
|
||||
cancelled = true;
|
||||
return;
|
||||
case IGNORE:
|
||||
cancelledStartGame = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
List<DownloadTask> nonFailedFirstTasks = tasks.stream().filter(t -> t.getException() == null).collect(Collectors.toList());
|
||||
List<DownloadTask> optionTasks = nonFailedFirstTasks.stream().filter(DownloadTask::correctSide).filter(DownloadTask::isOptional).collect(Collectors.toList());
|
||||
// If options changed, present all options again
|
||||
if (stateHandler.getOptionsButton() || optionTasks.stream().anyMatch(DownloadTask::isNewOptional)) {
|
||||
// new ArrayList is requires so it's an IOptionDetails rather than a DownloadTask list
|
||||
Future<Boolean> cancelledResult = ui.showOptions(new ArrayList<>(optionTasks));
|
||||
try {
|
||||
if (cancelledResult.get()) {
|
||||
cancelled = true;
|
||||
// TODO: Should the UI be closed somehow??
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
// Interrupted means cancelled???
|
||||
ui.handleExceptionAndExit(e);
|
||||
}
|
||||
}
|
||||
ui.disableOptionsButton();
|
||||
|
||||
// TODO: different thread pool type?
|
||||
ExecutorService threadPool = Executors.newFixedThreadPool(10);
|
||||
CompletionService<DownloadTask> completionService = new ExecutorCompletionService<>(threadPool);
|
||||
|
||||
tasks.forEach(t -> completionService.submit(() -> {
|
||||
t.download(opts.packFolder, indexUri);
|
||||
return t;
|
||||
}));
|
||||
|
||||
for (int i = 0; i < tasks.size(); i++) {
|
||||
DownloadTask task;
|
||||
try {
|
||||
task = completionService.take().get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
ui.handleException(e);
|
||||
task = null;
|
||||
}
|
||||
// Update manifest - If there were no errors cachedFile has already been modified in place (good old pass by reference)
|
||||
if (task != null) {
|
||||
if (task.getException() != null) {
|
||||
ManifestFile.File file = task.cachedFile.getRevert();
|
||||
if (file != null) {
|
||||
manifest.getCachedFiles().putIfAbsent(task.metadata.getFile(), file);
|
||||
}
|
||||
} else {
|
||||
// idiot, if it wasn't there in the first place it won't magically appear there
|
||||
manifest.getCachedFiles().putIfAbsent(task.metadata.getFile(), task.cachedFile);
|
||||
}
|
||||
}
|
||||
|
||||
String progress;
|
||||
if (task != null) {
|
||||
if (task.getException() != null) {
|
||||
progress = "Failed to download " + task.metadata.getName() + ": " + task.getException().getMessage();
|
||||
task.getException().printStackTrace();
|
||||
} else {
|
||||
// TODO: should this be revised for tasks that didn't actually download it?
|
||||
progress = "Downloaded " + task.metadata.getName();
|
||||
}
|
||||
} else {
|
||||
progress = "Failed to download, unknown reason";
|
||||
}
|
||||
ui.submitProgress(new InstallProgress(progress, i + 1, tasks.size()));
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
// Stop all tasks, don't launch the game (it's in an invalid state!)
|
||||
threadPool.shutdown();
|
||||
cancelled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Shut down the thread pool when the update is done
|
||||
threadPool.shutdown();
|
||||
|
||||
List<IExceptionDetails> failedTasks2ElectricBoogaloo = nonFailedFirstTasks.stream().filter(t -> t.getException() != null).collect(Collectors.toList());
|
||||
if (!failedTasks2ElectricBoogaloo.isEmpty()) {
|
||||
errorsOccurred = true;
|
||||
IExceptionDetails.ExceptionListResult exceptionListResult;
|
||||
try {
|
||||
exceptionListResult = ui.showExceptions(failedTasks2ElectricBoogaloo, tasks.size(), false).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
// Interrupted means cancelled???
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
switch (exceptionListResult) {
|
||||
case CONTINUE:
|
||||
break;
|
||||
case CANCEL:
|
||||
cancelled = true;
|
||||
return;
|
||||
case IGNORE:
|
||||
cancelledStartGame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showCancellationDialog() {
|
||||
IExceptionDetails.ExceptionListResult exceptionListResult;
|
||||
try {
|
||||
exceptionListResult = ui.showCancellationDialog().get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
// Interrupted means cancelled???
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
switch (exceptionListResult) {
|
||||
case CONTINUE:
|
||||
throw new RuntimeException("Continuation not allowed here!");
|
||||
case CANCEL:
|
||||
cancelled = true;
|
||||
return;
|
||||
case IGNORE:
|
||||
cancelledStartGame = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCancellation() {
|
||||
if (cancelled) {
|
||||
System.out.println("Update cancelled by user!");
|
||||
System.exit(1);
|
||||
} else if (cancelledStartGame) {
|
||||
System.out.println("Update cancelled by user! Continuing to start game...");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user