mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-01-12 14:14:52 +08:00
Modpack updates
This commit is contained in:
parent
12fa94627d
commit
e8316de160
@ -17,41 +17,58 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DependencyManager;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class HMCLModpackInstallTask extends Task {
|
||||
private final File zipFile;
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final Modpack modpack;
|
||||
private final File run;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String id) {
|
||||
dependency = profile.getDependency();
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) {
|
||||
DependencyManager dependency = profile.getDependency();
|
||||
repository = profile.getRepository();
|
||||
this.zipFile = zipFile;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.modpack = modpack;
|
||||
this.run = repository.getRunDirectory(name);
|
||||
|
||||
if (repository.hasVersion(id))
|
||||
throw new IllegalArgumentException("Version " + id + " already exists");
|
||||
File json = new File(run, "modpack.json");
|
||||
if (repository.hasVersion(name) && !json.exists())
|
||||
throw new IllegalArgumentException("Version " + name + " already exists");
|
||||
|
||||
dependents.add(dependency.gameBuilder().name(id).gameVersion(modpack.getGameVersion()).buildAsync());
|
||||
dependents.add(dependency.gameBuilder().name(name).gameVersion(modpack.getGameVersion()).buildAsync());
|
||||
|
||||
onDone().register(event -> {
|
||||
if (event.isFailed()) repository.removeVersionFromDisk(id);
|
||||
if (event.isFailed()) repository.removeVersionFromDisk(name);
|
||||
});
|
||||
|
||||
ModpackConfiguration<Modpack> config = null;
|
||||
try {
|
||||
if (json.exists())
|
||||
config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>(){}.getType());
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, "minecraft/", it -> !Objects.equals(it, "minecraft/pack.json"), config));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -69,8 +86,6 @@ public final class HMCLModpackInstallTask extends Task {
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version version = Constants.GSON.fromJson(json, Version.class).setJar(null);
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
|
||||
CompressingUtils.unzip(zipFile, repository.getRunDirectory(id),
|
||||
"minecraft/", it -> !Objects.equals(it, "minecraft/pack.json"), false);
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, "minecraft/", modpack, new File(run, "modpack.json")));
|
||||
}
|
||||
}
|
||||
|
@ -33,10 +33,26 @@ import java.util.List;
|
||||
*/
|
||||
public final class HMCLModpackManager {
|
||||
|
||||
public static final List<String> MODPACK_BLACK_LIST = Arrays.asList("usernamecache.json", "asm", "logs", "backups", "versions", "assets", "usercache.json", "libraries", "crash-reports", "launcher_profiles.json", "NVIDIA", "AMD", "TCNodeTracker", "screenshots", "natives", "native", "$native", "pack.json", "launcher.jar", "minetweaker.log", "launcher.pack.lzma", "hmclmc.log");
|
||||
public static final List<String> MODPACK_SUGGESTED_BLACK_LIST = Arrays.asList("fonts", "saves", "servers.dat", "options.txt", "optionsof.txt", "journeymap", "optionsshaders.txt", "mods/VoxelMods");
|
||||
public static final List<String> MODPACK_BLACK_LIST = Arrays.asList(
|
||||
"usernamecache.json", "usercache.json", // Minecraft
|
||||
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
|
||||
"pack.json", "launcher.jar", "hmclmc.log", // HMCL
|
||||
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
|
||||
"minetweaker.log", // Mods
|
||||
"logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
||||
"downloads", // Curse
|
||||
"asm", "backups", "TCNodeTracker", "CustomDISkins", "data" // Mods
|
||||
);
|
||||
public static final List<String> MODPACK_SUGGESTED_BLACK_LIST = Arrays.asList(
|
||||
"fonts", // BetterFonts
|
||||
"saves", "servers.dat", "options.txt", // Minecraft
|
||||
"blueprints" /* BuildCraft */,
|
||||
"optionsof.txt" /* OptiFine */,
|
||||
"journeymap" /* JourneyMap */,
|
||||
"optionsshaders.txt",
|
||||
"mods/VoxelMods");
|
||||
|
||||
public static ModAdviser MODPACK_PREDICATE = (String fileName, boolean isDirectory) -> {
|
||||
public static ModAdviser.ModSuggestion suggestMod(String fileName, boolean isDirectory) {
|
||||
if (match(MODPACK_BLACK_LIST, fileName, isDirectory))
|
||||
return ModAdviser.ModSuggestion.HIDDEN;
|
||||
if (match(MODPACK_SUGGESTED_BLACK_LIST, fileName, isDirectory))
|
||||
|
@ -61,7 +61,7 @@ public final class ModpackHelper {
|
||||
}
|
||||
|
||||
if (c.isOverrideMemory()) {
|
||||
vs.setPermSize(Optional.ofNullable(c.getPermGen()).map(i -> i.toString()).orElse(""));
|
||||
vs.setPermSize(Optional.ofNullable(c.getPermGen()).map(Object::toString).orElse(""));
|
||||
if (c.getMaxMemory() != null)
|
||||
vs.setMaxMemory(c.getMaxMemory());
|
||||
vs.setMinMemory(c.getMinMemory());
|
||||
|
@ -405,6 +405,10 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
private Node nowPage;
|
||||
|
||||
public void showPage(Node content) {
|
||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
||||
if (content != null)
|
||||
contentPlaceHolder.getStyleClass().add("gray-background");
|
||||
|
||||
Node c = content == null ? mainPage : content;
|
||||
onEnd();
|
||||
if (nowPage instanceof DecoratorPage)
|
||||
@ -457,6 +461,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
@Override
|
||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
||||
contentPlaceHolder.getStyleClass().add("white-background");
|
||||
setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.MultiMCModpackInstallTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@ -87,6 +88,8 @@ public final class TaskListPane extends StackPane {
|
||||
task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse")));
|
||||
} else if (task instanceof HMCLModpackExportTask) {
|
||||
task.setName(Main.i18n("modpack.export"));
|
||||
} else if (task instanceof MinecraftInstanceTask) {
|
||||
task.setName(Main.i18n("modpack.scan"));
|
||||
}
|
||||
|
||||
ProgressListNode node = new ProgressListNode(task);
|
||||
|
@ -983,6 +983,14 @@
|
||||
-fx-border-width: 1;
|
||||
}
|
||||
|
||||
.gray-background {
|
||||
-fx-background-color: rgba(244, 244, 244, 0.5);
|
||||
}
|
||||
|
||||
.white-background {
|
||||
-fx-background-color: rgba(255, 255, 255);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Tree Table View *
|
||||
|
@ -23,7 +23,7 @@
|
||||
<StackPane minWidth="200" maxWidth="200" styleClass="jfx-decorator-content-container">
|
||||
<BorderPane fx:id="leftRootPane">
|
||||
<center>
|
||||
<BorderPane style="-fx-background-color: rgba(244, 244, 244, 0.5);">
|
||||
<BorderPane styleClass="gray-background">
|
||||
<center>
|
||||
<AdvancedListBox fx:id="leftPane"/>
|
||||
</center>
|
||||
|
@ -412,3 +412,4 @@ extension.mod=Mod file
|
||||
extension.png=Image file
|
||||
message.success=Tasks succeeded
|
||||
message.doing=Please wait
|
||||
modpack.scan=Scanning this modpack
|
||||
|
@ -412,3 +412,4 @@ extension.mod=模组文件
|
||||
extension.png=图片文件
|
||||
message.success=已完成
|
||||
message.doing=请耐心等待
|
||||
modpack.scan=解析整合包
|
||||
|
@ -17,16 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Install a downloaded CurseForge modpack.
|
||||
@ -40,6 +46,8 @@ public final class CurseInstallTask extends Task {
|
||||
private final File zipFile;
|
||||
private final CurseManifest manifest;
|
||||
private final String name;
|
||||
private final File run;
|
||||
private final ModpackConfiguration<CurseManifest> config;
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
@ -57,10 +65,11 @@ public final class CurseInstallTask extends Task {
|
||||
this.zipFile = zipFile;
|
||||
this.manifest = manifest;
|
||||
this.name = name;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.run = repository.getRunDirectory(name);
|
||||
|
||||
repository = dependencyManager.getGameRepository();
|
||||
|
||||
if (repository.hasVersion(name))
|
||||
File json = new File(run, "modpack.json");
|
||||
if (repository.hasVersion(name) && !json.exists())
|
||||
throw new IllegalArgumentException("Version " + name + " already exists.");
|
||||
|
||||
GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getMinecraft().getGameVersion());
|
||||
@ -68,6 +77,15 @@ public final class CurseInstallTask extends Task {
|
||||
if (modLoader.getId().startsWith("forge-"))
|
||||
builder.version("forge", modLoader.getId().substring("forge-".length()));
|
||||
dependents.add(builder.buildAsync());
|
||||
|
||||
ModpackConfiguration<CurseManifest> config = null;
|
||||
try {
|
||||
if (json.exists())
|
||||
config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<CurseManifest>>(){}.getType());
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
this.config = config;
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, manifest.getOverrides(), Constants.truePredicate(), config));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -82,10 +100,17 @@ public final class CurseInstallTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
File run = repository.getRunDirectory(name);
|
||||
CompressingUtils.unzip(zipFile, run, manifest.getOverrides());
|
||||
if (config != null)
|
||||
for (CurseManifestFile oldCurseManifestFile : config.getManifest().getFiles()) {
|
||||
if (oldCurseManifestFile.getFileName() == null) continue;
|
||||
File oldFile = new File(run, "mods/" + oldCurseManifestFile.getFileName());
|
||||
if (!oldFile.exists()) continue;
|
||||
if (manifest.getFiles().stream().noneMatch(oldCurseManifestFile::equals))
|
||||
oldFile.delete();
|
||||
}
|
||||
|
||||
dependencies.add(new CurseCompletionTask(dependencyManager, name));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getOverrides(), false, manifest, new File(run, "modpack.json")));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.Validation;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -84,4 +85,18 @@ public final class CurseManifestFile implements Validation {
|
||||
public CurseManifestFile setFileName(String fileName) {
|
||||
return new CurseManifestFile(projectID, fileID, fileName, required);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CurseManifestFile that = (CurseManifestFile) o;
|
||||
return projectID == that.projectID &&
|
||||
fileID == that.fileID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(projectID, fileID);
|
||||
}
|
||||
}
|
||||
|
@ -17,5 +17,57 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
public class MinecraftInstanceTask {
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class MinecraftInstanceTask<T> extends Task {
|
||||
|
||||
private final File zipFile;
|
||||
private final String subDirectory;
|
||||
private final File jsonFile;
|
||||
private final T manifest;
|
||||
|
||||
public MinecraftInstanceTask(File zipFile, String subDirectory, T manifest, File jsonFile) {
|
||||
this.zipFile = zipFile;
|
||||
this.subDirectory = subDirectory;
|
||||
this.manifest = manifest;
|
||||
this.jsonFile = jsonFile;
|
||||
|
||||
if (!zipFile.exists())
|
||||
throw new IllegalArgumentException("File " + zipFile + " does not exist. Cannot parse this modpack.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Map<String, ModpackConfiguration.FileInformation> overrides = new HashMap<>();
|
||||
|
||||
byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
try (ZipArchiveInputStream zip = new ZipArchiveInputStream(new FileInputStream(zipFile), null, true, true)) {
|
||||
ArchiveEntry entry;
|
||||
while ((entry = zip.getNextEntry()) != null) {
|
||||
String path = entry.getName();
|
||||
if (!path.startsWith(subDirectory) || entry.isDirectory())
|
||||
continue;
|
||||
path = path.substring(subDirectory.length());
|
||||
if (path.startsWith("/") || path.startsWith("\\"))
|
||||
path = path.substring(1);
|
||||
|
||||
overrides.put(path, new ModpackConfiguration.FileInformation(
|
||||
path, DigestUtils.sha1Hex(zip)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
FileUtils.writeText(jsonFile, Constants.GSON.toJson(new ModpackConfiguration<>(manifest, overrides)));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Validation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@Immutable
|
||||
public final class ModpackConfiguration<T> implements Validation {
|
||||
|
||||
private final T manifest;
|
||||
|
||||
private final Map<String, FileInformation> overrides;
|
||||
|
||||
public ModpackConfiguration() {
|
||||
this(null, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public ModpackConfiguration(T manifest, Map<String, FileInformation> overrides) {
|
||||
this.manifest = manifest;
|
||||
this.overrides = overrides;
|
||||
}
|
||||
|
||||
public T getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
public ModpackConfiguration<T> setManifest(T manifest) {
|
||||
return new ModpackConfiguration<>(manifest, overrides);
|
||||
}
|
||||
|
||||
public ModpackConfiguration<T> setOverrides(Map<String, FileInformation> overrides) {
|
||||
return new ModpackConfiguration<>(manifest, overrides);
|
||||
}
|
||||
|
||||
public Map<String, FileInformation> getOverrides() {
|
||||
return Collections.unmodifiableMap(overrides);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (manifest == null)
|
||||
throw new JsonParseException("MinecraftInstanceConfiguration missing `manifest`");
|
||||
}
|
||||
|
||||
@Immutable
|
||||
public static class FileInformation implements Validation {
|
||||
private final String location; // relative
|
||||
private final String hash;
|
||||
private final String downloadURL;
|
||||
|
||||
public FileInformation() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public FileInformation(String location, String hash) {
|
||||
this(location, hash, null);
|
||||
}
|
||||
|
||||
public FileInformation(String location, String hash, String downloadURL) {
|
||||
this.location = location;
|
||||
this.hash = hash;
|
||||
this.downloadURL = downloadURL;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public String getDownloadURL() {
|
||||
return downloadURL;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (location == null)
|
||||
throw new JsonParseException("FileInformation missing `location`.");
|
||||
if (hash == null)
|
||||
throw new JsonParseException("FileInformation missing file hash code.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class ModpackInstallTask<T> extends Task {
|
||||
|
||||
private final File modpackFile;
|
||||
private final File dest;
|
||||
private final String subDirectory;
|
||||
private final Map<String, ModpackConfiguration.FileInformation> overrides;
|
||||
private final Predicate<String> callback;
|
||||
|
||||
public ModpackInstallTask(File modpackFile, File dest, String subDirectory, Predicate<String> callback, ModpackConfiguration<T> oldConfiguration) {
|
||||
this.modpackFile = modpackFile;
|
||||
this.dest = dest;
|
||||
this.subDirectory = subDirectory;
|
||||
this.callback = callback;
|
||||
|
||||
if (oldConfiguration == null)
|
||||
overrides = Collections.emptyMap();
|
||||
else
|
||||
overrides = oldConfiguration.getOverrides();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Set<String> entries = new HashSet<>();
|
||||
byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
if (!FileUtils.makeDirectory(dest))
|
||||
throw new IOException("Unable to make directory " + dest);
|
||||
try (ZipArchiveInputStream zipStream = new ZipArchiveInputStream(new FileInputStream(modpackFile), null, true, true)) {
|
||||
ArchiveEntry entry;
|
||||
while ((entry = zipStream.getNextEntry()) != null) {
|
||||
String path = entry.getName();
|
||||
|
||||
if (!path.startsWith(subDirectory))
|
||||
continue;
|
||||
path = path.substring(subDirectory.length());
|
||||
if (path.startsWith("/") || path.startsWith("\\"))
|
||||
path = path.substring(1);
|
||||
File entryFile = new File(dest, path);
|
||||
|
||||
if (callback != null)
|
||||
if (!callback.test(path))
|
||||
continue;
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (!FileUtils.makeDirectory(entryFile))
|
||||
throw new IOException("Unable to make directory: " + entryFile);
|
||||
} else {
|
||||
if (!FileUtils.makeDirectory(entryFile.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Unable to make parent directory for file " + entryFile);
|
||||
|
||||
entries.add(path);
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(IOUtils.DEFAULT_BUFFER_SIZE);
|
||||
IOUtils.copyTo(zipStream, os, buf);
|
||||
byte[] data = os.toByteArray();
|
||||
|
||||
if (!overrides.containsKey(path) || entryFile.exists()) {
|
||||
String oldHash = DigestUtils.sha1Hex(new FileInputStream(entryFile));
|
||||
String newHash = DigestUtils.sha1Hex(new ByteArrayInputStream(data));
|
||||
if (!oldHash.equals(newHash)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(entryFile)) {
|
||||
IOUtils.copyTo(new ByteArrayInputStream(data), fos, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String path : overrides.keySet()) {
|
||||
File original = new File(dest, path);
|
||||
if (original.exists() && !entries.contains(path))
|
||||
original.delete();
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
@ -25,12 +27,10 @@ import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -57,14 +57,24 @@ public final class MultiMCModpackInstallTask extends Task {
|
||||
this.name = name;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.run = repository.getRunDirectory(name);
|
||||
|
||||
if (repository.hasVersion(name))
|
||||
|
||||
File json = new File(run, "modpack.json");
|
||||
if (repository.hasVersion(name) && !json.exists())
|
||||
throw new IllegalArgumentException("Version " + name + " already exists.");
|
||||
dependents.add(dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion()).buildAsync());
|
||||
onDone().register(event -> {
|
||||
if (event.isFailed())
|
||||
repository.removeVersionFromDisk(name);
|
||||
});
|
||||
|
||||
ModpackConfiguration<MultiMCInstanceConfiguration> config = null;
|
||||
try {
|
||||
if (json.exists())
|
||||
config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<MultiMCInstanceConfiguration>>(){}.getType());
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, manifest.getName() + "/minecraft/", Constants.truePredicate(), config));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -80,8 +90,7 @@ public final class MultiMCModpackInstallTask extends Task {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Version version = Objects.requireNonNull(repository.readVersionJson(name));
|
||||
CompressingUtils.unzip(zipFile, run, manifest.getName() + "/minecraft/", null, false, true);
|
||||
|
||||
|
||||
try (ZipFile zip = new ZipFile(zipFile)) {
|
||||
for (ZipArchiveEntry entry : Lang.asIterable(zip.getEntries())) {
|
||||
// ensure that this entry is in folder 'patches' and is a json file.
|
||||
@ -99,8 +108,9 @@ public final class MultiMCModpackInstallTask extends Task {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getName() + "/minecraft/", manifest, new File(run, "modpack.json")));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -107,6 +107,10 @@ public class FileDownloadTask extends Task {
|
||||
return url;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
URL currentURL = url;
|
||||
@ -189,12 +193,12 @@ public class FileDownloadTask extends Task {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
} else {
|
||||
if (file.exists())
|
||||
file.delete();
|
||||
if (file.exists() || !file.delete())
|
||||
throw new IOException("Unable to delete existent file " + file);
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Cannot make parent directory " + file);
|
||||
throw new IOException("Unable to make parent directory " + file);
|
||||
if (!temp.renameTo(file))
|
||||
throw new IOException("Cannot move temp file to " + file);
|
||||
throw new IOException("Unable move temp file to " + file);
|
||||
}
|
||||
|
||||
if (downloaded != contentLength)
|
||||
|
@ -52,6 +52,16 @@ public abstract class Task {
|
||||
this.significance = significance;
|
||||
}
|
||||
|
||||
private TaskState state = TaskState.READY;
|
||||
|
||||
public TaskState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
void setState(TaskState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheduler that decides how this task runs.
|
||||
*/
|
||||
@ -111,14 +121,14 @@ public abstract class Task {
|
||||
/**
|
||||
* The collection of sub-tasks that should execute **before** this task running.
|
||||
*/
|
||||
public Collection<Task> getDependents() {
|
||||
public Collection<? extends Task> getDependents() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of sub-tasks that should execute **after** this task running.
|
||||
*/
|
||||
public Collection<Task> getDependencies() {
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@ -298,4 +308,11 @@ public abstract class Task {
|
||||
return this == MAJOR;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TaskState {
|
||||
READY,
|
||||
RUNNING,
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ public final class TaskExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean executeTasks(Collection<Task> tasks) throws InterruptedException {
|
||||
private boolean executeTasks(Collection<? extends Task> tasks) throws InterruptedException {
|
||||
if (tasks.isEmpty())
|
||||
return true;
|
||||
|
||||
@ -140,8 +140,12 @@ public final class TaskExecutor {
|
||||
}
|
||||
|
||||
private boolean executeTask(Task task) {
|
||||
if (canceled)
|
||||
if (canceled) {
|
||||
task.setState(Task.TaskState.FAILED);
|
||||
return false;
|
||||
}
|
||||
|
||||
task.setState(Task.TaskState.RUNNING);
|
||||
|
||||
if (task.getSignificance().shouldLog())
|
||||
Logging.LOG.log(Level.FINE, "Executing task: {0}", task.getName());
|
||||
@ -165,7 +169,7 @@ public final class TaskExecutor {
|
||||
|
||||
if (!executeTasks(task.getDependencies()) && task.isRelyingOnDependencies()) {
|
||||
Logging.LOG.severe("Subtasks failed for " + task.getName());
|
||||
return false;
|
||||
throw new SilentException();
|
||||
}
|
||||
|
||||
flag = true;
|
||||
@ -182,10 +186,8 @@ public final class TaskExecutor {
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
}
|
||||
} catch (SilentException e) {
|
||||
} catch (SilentException | RejectedExecutionException e) {
|
||||
// do nothing
|
||||
} catch (RejectedExecutionException e) {
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
lastException = e;
|
||||
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||
@ -194,6 +196,7 @@ public final class TaskExecutor {
|
||||
} finally {
|
||||
task.setVariables(null);
|
||||
}
|
||||
task.setState(flag ? Task.TaskState.SUCCEEDED : Task.TaskState.FAILED);
|
||||
return flag;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ public final class CompressingUtils {
|
||||
* @param sourceDir the source directory or a file.
|
||||
* @param zipFile the location of dest zip file.
|
||||
* @param pathNameCallback callback(pathName, isDirectory) returns your modified pathName
|
||||
* @throws IOException
|
||||
* @throws IOException if there is filesystem error.
|
||||
*/
|
||||
public static void zip(File sourceDir, File zipFile, BiFunction<String, Boolean, String> pathNameCallback) throws IOException {
|
||||
try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(new FileOutputStream(zipFile))) {
|
||||
@ -71,6 +71,8 @@ public final class CompressingUtils {
|
||||
File[] files = src.isDirectory() ? src.listFiles() : new File[] { src };
|
||||
String pathName;// the relative path (relative to the root directory to be compressed)
|
||||
byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
|
||||
if (files == null) return;
|
||||
for (File file : files)
|
||||
if (file.isDirectory()) {
|
||||
pathName = file.getPath().substring(basePath.length() + 1) + "/";
|
||||
@ -152,48 +154,39 @@ public final class CompressingUtils {
|
||||
* @param callback will be called for every entry in the zip file, returns false if you dont want this file being uncompressed.
|
||||
* @param ignoreExistentFile true if skip all existent files.
|
||||
* @param allowStoredEntriesWithDataDescriptor whether the zip stream will try to read STORED entries that use a data descriptor
|
||||
* @throws IOException
|
||||
* @throws IOException if zip file is malformed or filesystem error.
|
||||
*/
|
||||
public static void unzip(File src, File dest, String subDirectory, Predicate<String> callback, boolean ignoreExistentFile, boolean allowStoredEntriesWithDataDescriptor) throws IOException {
|
||||
byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
dest.mkdirs();
|
||||
try (ZipArchiveInputStream zipFile = new ZipArchiveInputStream(new FileInputStream(src), null, true, allowStoredEntriesWithDataDescriptor)) {
|
||||
if (src.exists()) {
|
||||
String strPath, gbkPath, strtemp;
|
||||
strPath = dest.getAbsolutePath();
|
||||
ArchiveEntry zipEnt;
|
||||
while ((zipEnt = zipFile.getNextEntry()) != null) {
|
||||
gbkPath = zipEnt.getName();
|
||||
if (!FileUtils.makeDirectory(dest))
|
||||
throw new IOException("Unable to make directory " + dest);
|
||||
try (ZipArchiveInputStream zipStream = new ZipArchiveInputStream(new FileInputStream(src), null, true, allowStoredEntriesWithDataDescriptor)) {
|
||||
ArchiveEntry entry;
|
||||
while ((entry = zipStream.getNextEntry()) != null) {
|
||||
String path = entry.getName();
|
||||
|
||||
if (!gbkPath.startsWith(subDirectory))
|
||||
if (!path.startsWith(subDirectory))
|
||||
continue;
|
||||
path = path.substring(subDirectory.length());
|
||||
if (path.startsWith("/") || path.startsWith("\\"))
|
||||
path = path.substring(1);
|
||||
File entryFile = new File(dest, path);
|
||||
|
||||
if (callback != null)
|
||||
if (!callback.test(path))
|
||||
continue;
|
||||
gbkPath = gbkPath.substring(subDirectory.length());
|
||||
if (gbkPath.startsWith("/") || gbkPath.startsWith("\\"))
|
||||
gbkPath = gbkPath.substring(1);
|
||||
strtemp = strPath + File.separator + gbkPath;
|
||||
|
||||
if (callback != null)
|
||||
if (!callback.test(gbkPath))
|
||||
continue;
|
||||
if (entry.isDirectory()) {
|
||||
if (!FileUtils.makeDirectory(entryFile))
|
||||
throw new IOException("Unable to make directory: " + entryFile);
|
||||
} else {
|
||||
if (!FileUtils.makeDirectory(entryFile.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Unable to make parent directory for file " + entryFile);
|
||||
|
||||
if (zipEnt.isDirectory()) {
|
||||
File dir = new File(strtemp);
|
||||
dir.mkdirs();
|
||||
} else {
|
||||
// create directories
|
||||
String strsubdir = gbkPath;
|
||||
for (int i = 0; i < strsubdir.length(); i++)
|
||||
if (strsubdir.substring(i, i + 1).equalsIgnoreCase("/")) {
|
||||
String temp = strPath + File.separator + strsubdir.substring(0, i);
|
||||
File subdir = new File(temp);
|
||||
if (!subdir.exists())
|
||||
subdir.mkdir();
|
||||
}
|
||||
if (ignoreExistentFile && new File(strtemp).exists())
|
||||
continue;
|
||||
try (FileOutputStream fos = new FileOutputStream(new File(strtemp))) {
|
||||
IOUtils.copyTo(zipFile, fos, buf);
|
||||
}
|
||||
if (ignoreExistentFile && entryFile.exists())
|
||||
continue;
|
||||
try (FileOutputStream fos = new FileOutputStream(entryFile)) {
|
||||
IOUtils.copyTo(zipStream, fos, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Constants.
|
||||
@ -86,4 +87,11 @@ public final class Constants {
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory.INSTANCE)
|
||||
.create();
|
||||
|
||||
public static <T> Predicate<T> truePredicate() {
|
||||
return s -> true;
|
||||
}
|
||||
|
||||
public static <T> Predicate<T> falsePredicate() {
|
||||
return s -> false;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user