mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-17 15:57:18 -04:00
Use ZipFileSystem instead of commons-compress
This commit is contained in:
parent
cadafe13e1
commit
51afcf2dee
@ -17,12 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.ZipEngine;
|
||||
import org.jackhuang.hmcl.util.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@ -31,58 +30,44 @@ import java.util.List;
|
||||
/**
|
||||
* Export the game to a mod pack file.
|
||||
*/
|
||||
public class HMCLModpackExportTask extends TaskResult<ZipEngine> {
|
||||
public class HMCLModpackExportTask extends Task {
|
||||
private final DefaultGameRepository repository;
|
||||
private final String version;
|
||||
private final List<String> whitelist;
|
||||
private final Modpack modpack;
|
||||
private final File output;
|
||||
private final String id;
|
||||
|
||||
public HMCLModpackExportTask(DefaultGameRepository repository, String version, List<String> whitelist, Modpack modpack, File output) {
|
||||
this(repository, version, whitelist, modpack, output, ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param output mod pack file.
|
||||
* @param version to locate version.json
|
||||
*/
|
||||
public HMCLModpackExportTask(DefaultGameRepository repository, String version, List<String> whitelist, Modpack modpack, File output, String id) {
|
||||
public HMCLModpackExportTask(DefaultGameRepository repository, String version, List<String> whitelist, Modpack modpack, File output) {
|
||||
this.repository = repository;
|
||||
this.version = version;
|
||||
this.whitelist = whitelist;
|
||||
this.modpack = modpack;
|
||||
this.output = output;
|
||||
this.id = id;
|
||||
|
||||
onDone().register(event -> {
|
||||
if (event.isFailed()) output.delete();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
ArrayList<String> blackList = new ArrayList<>(HMCLModpackManager.MODPACK_BLACK_LIST);
|
||||
blackList.add(version + ".jar");
|
||||
blackList.add(version + ".json");
|
||||
Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
|
||||
try (ZipEngine zip = new ZipEngine(output)) {
|
||||
zip.putDirectory(repository.getRunDirectory(version), (String pathName, Boolean isDirectory) -> {
|
||||
try (Zipper zip = new Zipper(output.toPath())) {
|
||||
zip.putDirectory(repository.getRunDirectory(version).toPath(), "minecraft", path -> {
|
||||
for (String s : blackList)
|
||||
if (isDirectory) {
|
||||
if (pathName.startsWith(s + "/"))
|
||||
return null;
|
||||
} else if (pathName.equals(s))
|
||||
return null;
|
||||
if (path.equals(s))
|
||||
return false;
|
||||
for (String s : whitelist)
|
||||
if (pathName.equals(s + (isDirectory ? "/" : "")))
|
||||
return "minecraft/" + pathName;
|
||||
return null;
|
||||
if (path.equals(s))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
Version mv = repository.getResolvedVersion(version);
|
||||
@ -92,6 +77,4 @@ public class HMCLModpackExportTask extends TaskResult<ZipEngine> {
|
||||
zip.putTextFile(Constants.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
|
||||
}
|
||||
}
|
||||
|
||||
public static final String ID = "zip_engine";
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ public final class HMCLModpackInstallTask extends Task {
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, "minecraft/", it -> !Objects.equals(it, "minecraft/pack.json"), config));
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, "/minecraft", it -> !"pack.json".equals(it), config));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -90,9 +90,9 @@ public final class HMCLModpackInstallTask extends Task {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version version = Constants.GSON.fromJson(json, Version.class).setJar(null);
|
||||
Version version = Constants.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, "minecraft/", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "HMCL";
|
||||
|
@ -39,7 +39,7 @@ public final class HMCLModpackManager {
|
||||
"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
|
||||
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
||||
"downloads", // Curse
|
||||
"asm", "backups", "TCNodeTracker", "CustomDISkins", "data" // Mods
|
||||
);
|
||||
|
@ -86,11 +86,13 @@ public final class ModpackHelper {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
|
||||
FinalizedCallback finalizeTask = (variables, isDependentsSucceeded) -> {
|
||||
profile.getRepository().refreshVersions();
|
||||
VersionSetting vs = profile.specializeVersionSetting(name);
|
||||
profile.getRepository().undoMark(name);
|
||||
if (vs != null)
|
||||
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||
if (isDependentsSucceeded) {
|
||||
profile.getRepository().refreshVersions();
|
||||
VersionSetting vs = profile.specializeVersionSetting(name);
|
||||
profile.getRepository().undoMark(name);
|
||||
if (vs != null)
|
||||
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||
}
|
||||
};
|
||||
|
||||
if (modpack.getManifest() instanceof CurseManifest)
|
||||
@ -102,7 +104,7 @@ public final class ModpackHelper {
|
||||
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
|
||||
.finalized(finalizeTask)
|
||||
.with(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
|
||||
.then(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
|
||||
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
|
||||
} catch (UnsupportedModpackException e) {
|
||||
txtModpackName.setText(i18n("modpack.task.install.error"));
|
||||
btnInstall.setDisable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.ZipEngine;
|
||||
import org.jackhuang.hmcl.util.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
@ -81,7 +81,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
dependency = dependency.then(Task.of(() -> {
|
||||
boolean flag = true;
|
||||
|
||||
try (ZipEngine zip = new ZipEngine(modpackFile)) {
|
||||
try (Zipper zip = new Zipper(modpackFile.toPath())) {
|
||||
Config exported = new Config();
|
||||
exported.setBackgroundImageType(config().getBackgroundImageType());
|
||||
exported.setBackgroundImage(config().getBackgroundImage());
|
||||
@ -93,7 +93,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
|
||||
File bg = new File("bg").getAbsoluteFile();
|
||||
if (bg.isDirectory())
|
||||
zip.putDirectory(bg);
|
||||
zip.putDirectory(bg.toPath(), "bg");
|
||||
|
||||
File background_png = new File("background.png").getAbsoluteFile();
|
||||
if (background_png.isFile())
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
@ -10,6 +9,7 @@ import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
@ -145,7 +145,7 @@ public final class LibraryDownloadTask extends Task {
|
||||
if (!dest.delete())
|
||||
throw new IOException("Unable to delete file " + dest);
|
||||
|
||||
byte[] decompressed = IOUtils.readFullyAsByteArray(new XZCompressorInputStream(new ByteArrayInputStream(src)));
|
||||
byte[] decompressed = IOUtils.readFullyAsByteArray(new XZInputStream(new ByteArrayInputStream(src)));
|
||||
|
||||
String end = new String(decompressed, decompressed.length - 4, 4);
|
||||
if (!end.equals("SIGN"))
|
||||
|
@ -148,10 +148,10 @@ public class DefaultGameRepository implements GameRepository {
|
||||
}
|
||||
|
||||
public boolean removeVersionFromDisk(String id) {
|
||||
if (!versions.containsKey(id))
|
||||
return true;
|
||||
if (EventBus.EVENT_BUS.fireEvent(new RemoveVersionEvent(this, id)) == Event.Result.DENY)
|
||||
return false;
|
||||
if (!versions.containsKey(id))
|
||||
return FileUtils.deleteDirectoryQuietly(getVersionRoot(id));
|
||||
File file = getVersionRoot(id);
|
||||
if (!file.exists())
|
||||
return true;
|
||||
|
@ -17,8 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantPool;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantType;
|
||||
@ -26,6 +25,9 @@ import org.jenkinsci.constant_pool_scanner.StringConstant;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@ -35,8 +37,8 @@ import java.util.stream.StreamSupport;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameVersion {
|
||||
private static Optional<String> getVersionOfClassMinecraft(ZipFile file, ZipArchiveEntry entry) throws IOException {
|
||||
ConstantPool pool = ConstantPoolScanner.parse(file.getInputStream(entry), ConstantType.STRING);
|
||||
private static Optional<String> getVersionOfClassMinecraft(byte[] bytecode) throws IOException {
|
||||
ConstantPool pool = ConstantPoolScanner.parse(bytecode, ConstantType.STRING);
|
||||
|
||||
return StreamSupport.stream(pool.list(StringConstant.class).spliterator(), false)
|
||||
.map(StringConstant::get)
|
||||
@ -45,8 +47,8 @@ public final class GameVersion {
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static Optional<String> getVersionFromClassMinecraftServer(ZipFile file, ZipArchiveEntry entry) throws IOException {
|
||||
ConstantPool pool = ConstantPoolScanner.parse(file.getInputStream(entry), ConstantType.STRING);
|
||||
private static Optional<String> getVersionFromClassMinecraftServer(byte[] bytecode) throws IOException {
|
||||
ConstantPool pool = ConstantPoolScanner.parse(bytecode, ConstantType.STRING);
|
||||
|
||||
List<String> list = StreamSupport.stream(pool.list(StringConstant.class).spliterator(), false)
|
||||
.map(StringConstant::get)
|
||||
@ -72,16 +74,16 @@ public final class GameVersion {
|
||||
return Optional.empty();
|
||||
|
||||
try {
|
||||
try (ZipFile gameJar = new ZipFile(file)) {
|
||||
ZipArchiveEntry minecraft = gameJar.getEntry("net/minecraft/client/Minecraft.class");
|
||||
if (minecraft != null) {
|
||||
Optional<String> result = getVersionOfClassMinecraft(gameJar, minecraft);
|
||||
try (FileSystem gameJar = CompressingUtils.createReadOnlyZipFileSystem(file.toPath())) {
|
||||
Path minecraft = gameJar.getPath("net/minecraft/client/Minecraft.class");
|
||||
if (Files.exists(minecraft)) {
|
||||
Optional<String> result = getVersionOfClassMinecraft(Files.readAllBytes(minecraft));
|
||||
if (result.isPresent())
|
||||
return result;
|
||||
}
|
||||
ZipArchiveEntry minecraftServer = gameJar.getEntry("net/minecraft/server/MinecraftServer.class");
|
||||
if (minecraftServer != null)
|
||||
return getVersionFromClassMinecraftServer(gameJar, minecraftServer);
|
||||
Path minecraftServer = gameJar.getPath("net/minecraft/server/MinecraftServer.class");
|
||||
if (Files.exists(minecraftServer))
|
||||
return getVersionFromClassMinecraftServer(Files.readAllBytes(minecraftServer));
|
||||
return Optional.empty();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
@ -229,11 +229,9 @@ public class DefaultLauncher extends Launcher {
|
||||
try {
|
||||
for (Library library : version.getLibraries())
|
||||
if (library.isNative())
|
||||
CompressingUtils.unzip(repository.getLibraryFile(version, library),
|
||||
destination,
|
||||
"",
|
||||
library.getExtract()::shouldExtract,
|
||||
false);
|
||||
new Unzipper(repository.getLibraryFile(version, library), destination)
|
||||
.setFilter((destFile, isDirectory, zipEntry, path) -> library.getExtract().shouldExtract(path))
|
||||
.setReplaceExistentFile(true).unzip();
|
||||
} catch (IOException e) {
|
||||
throw new NotDecompressingNativesException(e);
|
||||
}
|
||||
|
@ -76,6 +76,10 @@ public final class CurseInstallTask extends Task {
|
||||
builder.version("forge", modLoader.getId().substring("forge-".length()));
|
||||
dependents.add(builder.buildAsync());
|
||||
|
||||
onDone().register(event -> {
|
||||
if (event.isFailed()) repository.removeVersionFromDisk(name);
|
||||
});
|
||||
|
||||
ModpackConfiguration<CurseManifest> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
|
@ -20,15 +20,13 @@ package org.jackhuang.hmcl.mod;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
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.util.Constants;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -114,11 +112,11 @@ public final class ForgeModMetadata {
|
||||
}
|
||||
|
||||
public static ModInfo fromFile(File modFile) throws IOException, JsonParseException {
|
||||
try (ZipFile zipFile = new ZipFile(modFile)) {
|
||||
ZipArchiveEntry entry = zipFile.getEntry("mcmod.info");
|
||||
if (entry == null)
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) {
|
||||
Path mcmod = fs.getPath("mcmod.info");
|
||||
if (Files.notExists(mcmod))
|
||||
throw new IOException("File " + modFile + " is not a Forge mod.");
|
||||
List<ForgeModMetadata> modList = Constants.GSON.fromJson(IOUtils.readFullyAsString(zipFile.getInputStream(entry)),
|
||||
List<ForgeModMetadata> modList = Constants.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(mcmod)),
|
||||
new TypeToken<List<ForgeModMetadata>>() {
|
||||
}.getType());
|
||||
if (modList == null || modList.isEmpty())
|
||||
|
@ -17,14 +17,15 @@
|
||||
*/
|
||||
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.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@ -41,7 +42,7 @@ public final class MinecraftInstanceTask<T> extends Task {
|
||||
|
||||
public MinecraftInstanceTask(File zipFile, String subDirectory, T manifest, String type, File jsonFile) {
|
||||
this.zipFile = zipFile;
|
||||
this.subDirectory = subDirectory;
|
||||
this.subDirectory = FileUtils.normalizePath(subDirectory);
|
||||
this.manifest = manifest;
|
||||
this.jsonFile = jsonFile;
|
||||
this.type = type;
|
||||
@ -54,18 +55,18 @@ public final class MinecraftInstanceTask<T> extends Task {
|
||||
public void execute() throws Exception {
|
||||
List<ModpackConfiguration.FileInformation> overrides = new LinkedList<>();
|
||||
|
||||
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);
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(zipFile.toPath())) {
|
||||
Path root = fs.getPath(subDirectory);
|
||||
|
||||
overrides.add(new ModpackConfiguration.FileInformation(path, encodeHex(digest("SHA-1", zip))));
|
||||
}
|
||||
if (Files.exists(root))
|
||||
Files.walkFileTree(fs.getPath(subDirectory), new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
String relativePath = root.relativize(file).normalize().toString();
|
||||
overrides.add(new ModpackConfiguration.FileInformation(relativePath, encodeHex(digest("SHA-1", Files.newInputStream(file)))));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FileUtils.writeText(jsonFile, Constants.GSON.toJson(new ModpackConfiguration<>(manifest, type, overrides)));
|
||||
|
@ -17,17 +17,15 @@
|
||||
*/
|
||||
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.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Unzipper;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
@ -56,61 +54,36 @@ public class ModpackInstallTask<T> extends Task {
|
||||
@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);
|
||||
|
||||
HashSet<String> files = new HashSet<>();
|
||||
HashMap<String, ModpackConfiguration.FileInformation> files = new HashMap<>();
|
||||
for (ModpackConfiguration.FileInformation file : overrides)
|
||||
files.add(file.getPath());
|
||||
files.put(file.getPath(), file);
|
||||
|
||||
try (ZipArchiveInputStream zipStream = new ZipArchiveInputStream(new FileInputStream(modpackFile), null, true, true)) {
|
||||
ArchiveEntry entry;
|
||||
while ((entry = zipStream.getNextEntry()) != null) {
|
||||
String path = entry.getName();
|
||||
new Unzipper(modpackFile, dest)
|
||||
.setSubDirectory(subDirectory)
|
||||
.setTerminateIfSubDirectoryNotExists()
|
||||
.setReplaceExistentFile(true)
|
||||
.setFilter((destPath, isDirectory, zipEntry, entryPath) -> {
|
||||
if (isDirectory) return true;
|
||||
entries.add(entryPath);
|
||||
|
||||
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 (files.contains(path) && entryFile.exists()) {
|
||||
String oldHash = encodeHex(digest("SHA-1", new FileInputStream(entryFile)));
|
||||
String newHash = encodeHex(digest("SHA-1", new ByteArrayInputStream(data)));
|
||||
if (!oldHash.equals(newHash)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(entryFile)) {
|
||||
IOUtils.copyTo(new ByteArrayInputStream(data), fos, buf);
|
||||
}
|
||||
}
|
||||
} else if (!files.contains(path)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(entryFile)) {
|
||||
IOUtils.copyTo(new ByteArrayInputStream(data), fos, buf);
|
||||
}
|
||||
if (!files.containsKey(entryPath)) {
|
||||
// If old modpack does not have this entry, add this entry or override the file that user added.
|
||||
return true;
|
||||
} else if (!Files.exists(destPath)) {
|
||||
// If both old and new modpacks have this entry, but the file is deleted by user, leave it missing.
|
||||
return false;
|
||||
} else {
|
||||
// If user modified this entry file, we will not replace this file since this modified file is that user expects.
|
||||
String fileHash = encodeHex(digest("SHA-1", Files.newInputStream(destPath)));
|
||||
String oldHash = files.get(entryPath).getHash();
|
||||
return Objects.equals(oldHash, fileHash);
|
||||
}
|
||||
}).unzip();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If old modpack have this entry, and new modpack deleted it. Delete this file.
|
||||
for (ModpackConfiguration.FileInformation file : overrides) {
|
||||
File original = new File(dest, file.getPath());
|
||||
if (original.exists() && !entries.contains(file.getPath()))
|
||||
|
@ -17,14 +17,15 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
@ -262,15 +263,17 @@ public final class MultiMCInstanceConfiguration {
|
||||
return mmcPack;
|
||||
}
|
||||
|
||||
public static Modpack readMultiMCModpackManifest(File f) throws IOException {
|
||||
try (ZipFile zipFile = new ZipFile(f)) {
|
||||
ZipArchiveEntry firstEntry = zipFile.getEntries().nextElement();
|
||||
String name = StringUtils.substringBefore(firstEntry.getName(), '/');
|
||||
ZipArchiveEntry entry = zipFile.getEntry(name + "/instance.cfg");
|
||||
if (entry == null)
|
||||
throw new IOException("`instance.cfg` not found, " + f + " is not a valid MultiMC modpack.");
|
||||
MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(f);
|
||||
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, zipFile.getInputStream(entry), manifest);
|
||||
public static Modpack readMultiMCModpackManifest(File modpackFile) throws IOException {
|
||||
MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(modpackFile);
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modpackFile.toPath())) {
|
||||
Path root = Files.list(fs.getPath("/")).filter(Files::isDirectory).findAny()
|
||||
.orElseThrow(() -> new IOException("Not a valid MultiMC modpack"));
|
||||
String name = root.normalize().getFileName().toString();
|
||||
|
||||
Path instancePath = root.resolve("instance.cfg");
|
||||
if (Files.notExists(instancePath))
|
||||
throw new IOException("`instance.cfg` not found, " + modpackFile + " is not a valid MultiMC modpack.");
|
||||
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, Files.newInputStream(instancePath), manifest);
|
||||
return new Modpack(cfg.getName(), "", "", cfg.getGameVersion(), cfg.getNotes(), cfg);
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,17 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.JsonUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@Immutable
|
||||
@ -49,14 +52,14 @@ public final class MultiMCManifest {
|
||||
return components;
|
||||
}
|
||||
|
||||
public static MultiMCManifest readMultiMCModpackManifest(File f) throws IOException {
|
||||
try (ZipFile zipFile = new ZipFile(f)) {
|
||||
ZipArchiveEntry firstEntry = zipFile.getEntries().nextElement();
|
||||
String name = StringUtils.substringBefore(firstEntry.getName(), '/');
|
||||
ZipArchiveEntry entry = zipFile.getEntry(name + "/mmc-pack.json");
|
||||
if (entry == null)
|
||||
public static MultiMCManifest readMultiMCModpackManifest(File zipFile) throws IOException {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(zipFile.toPath())) {
|
||||
Path root = Files.list(fs.getPath("/")).filter(Files::isDirectory).findAny()
|
||||
.orElseThrow(() -> new IOException("Not a valid MultiMC modpack"));
|
||||
Path mmcPack = root.resolve("mmc-pack.json");
|
||||
if (Files.notExists(mmcPack))
|
||||
return null;
|
||||
String json = IOUtils.readFullyAsString(zipFile.getInputStream(entry));
|
||||
String json = IOUtils.readFullyAsString(Files.newInputStream(mmcPack));
|
||||
MultiMCManifest manifest = JsonUtils.fromNonNullJson(json, MultiMCManifest.class);
|
||||
|
||||
if (manifest != null && manifest.getComponents() == null)
|
||||
|
@ -19,8 +19,6 @@ 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;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
@ -28,13 +26,13 @@ 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.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
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.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -98,7 +96,7 @@ public final class MultiMCModpackInstallTask extends Task {
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, manifest.getName() + "/minecraft/", Constants.truePredicate(), config));
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, "/" + manifest.getName() + "/minecraft", Constants.truePredicate(), config));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -115,26 +113,30 @@ public final class MultiMCModpackInstallTask extends Task {
|
||||
public void execute() throws Exception {
|
||||
Version version = Objects.requireNonNull(repository.readVersionJson(name));
|
||||
|
||||
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.
|
||||
if (!entry.isDirectory() && entry.getName().startsWith(manifest.getName() + "/patches/") && entry.getName().endsWith(".json")) {
|
||||
MultiMCInstancePatch patch = Constants.GSON.fromJson(IOUtils.readFullyAsString(zip.getInputStream(entry)), MultiMCInstancePatch.class);
|
||||
List<String> newArguments = new LinkedList<>();
|
||||
for (String arg : patch.getTweakers()) {
|
||||
newArguments.add("--tweakClass");
|
||||
newArguments.add(arg);
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(zipFile.toPath())) {
|
||||
Path root = Files.list(fs.getPath("/")).filter(Files::isDirectory).findAny()
|
||||
.orElseThrow(() -> new IOException("Not a valid MultiMC modpack"));
|
||||
Path patches = root.resolve("patches");
|
||||
|
||||
if (Files.exists(patches))
|
||||
for (Path patchJson : Files.newDirectoryStream(patches)) {
|
||||
if (patchJson.endsWith(".json")) {
|
||||
MultiMCInstancePatch patch = Constants.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(patchJson)), MultiMCInstancePatch.class);
|
||||
List<String> newArguments = new LinkedList<>();
|
||||
for (String arg : patch.getTweakers()) {
|
||||
newArguments.add("--tweakClass");
|
||||
newArguments.add(arg);
|
||||
}
|
||||
version = version
|
||||
.setLibraries(Lang.merge(version.getLibraries(), patch.getLibraries()))
|
||||
.setMainClass(patch.getMainClass())
|
||||
.setArguments(version.getArguments().orElseGet(Arguments::new).addGameArguments(newArguments));
|
||||
}
|
||||
version = version
|
||||
.setLibraries(Lang.merge(version.getLibraries(), patch.getLibraries()))
|
||||
.setMainClass(patch.getMainClass())
|
||||
.setArguments(version.getArguments().orElseGet(Arguments::new).addGameArguments(newArguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getName() + "/minecraft/", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "MultiMC";
|
||||
|
@ -17,16 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Utilities of compressing
|
||||
@ -38,176 +38,44 @@ public final class CompressingUtils {
|
||||
private CompressingUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress the given directory to a zip file.
|
||||
*
|
||||
* @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 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))) {
|
||||
String basePath;
|
||||
if (sourceDir.isDirectory())
|
||||
basePath = sourceDir.getPath();
|
||||
else
|
||||
basePath = sourceDir.getParent();
|
||||
zipFile(sourceDir, basePath, zos, pathNameCallback);
|
||||
zos.closeArchiveEntry();
|
||||
}
|
||||
public static FileSystem createReadOnlyZipFileSystem(Path zipFile) throws IOException {
|
||||
return createReadOnlyZipFileSystem(zipFile, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip file.
|
||||
*
|
||||
* @param src source directory to be compressed.
|
||||
* @param basePath the file directory to be compressed, if [src] is a file, this is the parent directory of [src]
|
||||
* @param zos the [ZipOutputStream] of dest zip file.
|
||||
* @param pathNameCallback callback(pathName, isDirectory) returns your modified pathName, null if you dont want this file zipped
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
private static void zipFile(File src, String basePath,
|
||||
ZipArchiveOutputStream zos, BiFunction<String, Boolean, String> pathNameCallback) throws IOException {
|
||||
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) + "/";
|
||||
if (pathNameCallback != null)
|
||||
pathName = pathNameCallback.apply(pathName, true);
|
||||
if (pathName == null)
|
||||
continue;
|
||||
zos.putArchiveEntry(new ZipArchiveEntry(pathName));
|
||||
zipFile(file, basePath, zos, pathNameCallback);
|
||||
} else {
|
||||
pathName = file.getPath().substring(basePath.length() + 1);
|
||||
if (pathNameCallback != null)
|
||||
pathName = pathNameCallback.apply(pathName, true);
|
||||
if (pathName == null)
|
||||
continue;
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
zos.putArchiveEntry(new ZipArchiveEntry(pathName));
|
||||
IOUtils.copyTo(is, zos, buf);
|
||||
}
|
||||
}
|
||||
public static FileSystem createReadOnlyZipFileSystem(Path zipFile, String encoding) throws IOException {
|
||||
return createZipFileSystem(zipFile, false, false, encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress the given zip file to a directory.
|
||||
*
|
||||
* @param src the input zip file.
|
||||
* @param dest the dest directory.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static void unzip(File src, File dest) throws IOException {
|
||||
unzip(src, dest, "");
|
||||
public static FileSystem createWritableZipFileSystem(Path zipFile) throws IOException {
|
||||
return createWritableZipFileSystem(zipFile, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress the given zip file to a directory.
|
||||
*
|
||||
* @param src the input zip file.
|
||||
* @param dest the dest directory.
|
||||
* @param subDirectory the subdirectory of the zip file to be decompressed.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static void unzip(File src, File dest, String subDirectory) throws IOException {
|
||||
unzip(src, dest, subDirectory, null);
|
||||
public static FileSystem createWritableZipFileSystem(Path zipFile, String encoding) throws IOException {
|
||||
return createZipFileSystem(zipFile, true, true, encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress the given zip file to a directory.
|
||||
*
|
||||
* @param src the input zip file.
|
||||
* @param dest the dest directory.
|
||||
* @param subDirectory the subdirectory of the zip file to be decompressed.
|
||||
* @param callback will be called for every entry in the zip file, returns false if you dont want this file being uncompressed.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static void unzip(File src, File dest, String subDirectory, Predicate<String> callback) throws IOException {
|
||||
unzip(src, dest, subDirectory, callback, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress the given zip file to a directory.
|
||||
*
|
||||
* @param src the input zip file.
|
||||
* @param dest the dest directory.
|
||||
* @param subDirectory the subdirectory of the zip file to be decompressed.
|
||||
* @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.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static void unzip(File src, File dest, String subDirectory, Predicate<String> callback, boolean ignoreExistentFile) throws IOException {
|
||||
unzip(src, dest, subDirectory, callback, ignoreExistentFile, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress the given zip file to a directory.
|
||||
*
|
||||
* @param src the input zip file.
|
||||
* @param dest the dest directory.
|
||||
* @param subDirectory the subdirectory of the zip file to be decompressed.
|
||||
* @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 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];
|
||||
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 (!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);
|
||||
|
||||
if (ignoreExistentFile && entryFile.exists())
|
||||
continue;
|
||||
try (FileOutputStream fos = new FileOutputStream(entryFile)) {
|
||||
IOUtils.copyTo(zipStream, fos, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static FileSystem createZipFileSystem(Path zipFile, boolean create, boolean useTempFile, String encoding) throws IOException {
|
||||
Map<String, Object> env = new HashMap<>();
|
||||
if (create)
|
||||
env.put("create", "true");
|
||||
if (encoding != null)
|
||||
env.put("encoding", encoding);
|
||||
if (useTempFile)
|
||||
env.put("useTempFile", true);
|
||||
return FileSystems.newFileSystem(URI.create("jar:" + zipFile.toUri()), env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the text content of a file in zip.
|
||||
*
|
||||
* @param file the zip file
|
||||
* @param zipFile the zip file
|
||||
* @param name the location of the text in zip file, something like A/B/C/D.txt
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @return the content of given file.
|
||||
* @return the plain text content of given file.
|
||||
*/
|
||||
public static String readTextZipEntry(File file, String name) throws IOException {
|
||||
try (ZipFile zipFile = new ZipFile(file)) {
|
||||
ZipArchiveEntry entry = zipFile.getEntry(name);
|
||||
if (entry == null)
|
||||
throw new IOException("ZipEntry `" + name + "` not found in " + file);
|
||||
return IOUtils.readFullyAsString(zipFile.getInputStream(entry));
|
||||
public static String readTextZipEntry(File zipFile, String name) throws IOException {
|
||||
try (FileSystem fs = createReadOnlyZipFileSystem(zipFile.toPath())) {
|
||||
return new String(Files.readAllBytes(fs.getPath(name)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,7 +84,7 @@ public final class CompressingUtils {
|
||||
*
|
||||
* @param file the zip file
|
||||
* @param name the location of the text in zip file, something like A/B/C/D.txt
|
||||
* @return the content of given file.
|
||||
* @return the plain text content of given file.
|
||||
*/
|
||||
public static Optional<String> readTextZipEntryQuietly(File file, String name) {
|
||||
try {
|
||||
|
@ -17,11 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -45,6 +45,13 @@ public final class FileUtils {
|
||||
return StringUtils.substringAfterLast(file.getName(), '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is for normalizing ZipPath since Path.normalize of ZipFileSystem does not work properly.
|
||||
*/
|
||||
public static String normalizePath(String path) {
|
||||
return StringUtils.addPrefix(StringUtils.removeSuffix(path, "/", "\\"), "/");
|
||||
}
|
||||
|
||||
public static String readText(File file) throws IOException {
|
||||
return readText(file, UTF_8);
|
||||
}
|
||||
@ -54,11 +61,7 @@ public final class FileUtils {
|
||||
}
|
||||
|
||||
public static byte[] readBytes(File file) throws IOException {
|
||||
try (FileInputStream input = new FileInputStream(file)) {
|
||||
if (file.length() > Integer.MAX_VALUE)
|
||||
throw new OutOfMemoryError("File " + file + " is too big (" + file.length() + " bytes) to fit in memory.");
|
||||
return IOUtils.readFullyAsByteArray(input);
|
||||
}
|
||||
return Files.readAllBytes(file.toPath());
|
||||
}
|
||||
|
||||
public static void writeText(File file, String text) throws IOException {
|
||||
@ -123,16 +126,14 @@ public final class FileUtils {
|
||||
|
||||
public static void forceDelete(File file)
|
||||
throws IOException {
|
||||
if (file.isDirectory())
|
||||
if (file.isDirectory()) {
|
||||
deleteDirectory(file);
|
||||
else {
|
||||
} else {
|
||||
boolean filePresent = file.exists();
|
||||
if (!file.delete()) {
|
||||
if (!filePresent)
|
||||
throw new FileNotFoundException("File does not exist: " + file);
|
||||
String message = "Unable to delete file: " + file;
|
||||
|
||||
throw new IOException(message);
|
||||
throw new IOException("Unable to delete file: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,10 +154,6 @@ public final class FileUtils {
|
||||
return !fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile());
|
||||
}
|
||||
|
||||
public static void copyDirectory(Path src, Path dest) {
|
||||
|
||||
}
|
||||
|
||||
public static void copyFile(File srcFile, File destFile)
|
||||
throws IOException {
|
||||
Objects.requireNonNull(srcFile, "Source must not be null");
|
||||
|
@ -137,18 +137,28 @@ public final class StringUtils {
|
||||
return str;
|
||||
}
|
||||
|
||||
public static String removePrefix(String str, String prefix) {
|
||||
public static String addPrefix(String str, String prefix) {
|
||||
if (str.startsWith(prefix))
|
||||
return str.substring(prefix.length(), str.length());
|
||||
else
|
||||
return str;
|
||||
else
|
||||
return prefix + str;
|
||||
}
|
||||
|
||||
public static String removeSuffix(String str, String suffix) {
|
||||
if (str.endsWith(suffix))
|
||||
return str.substring(0, str.length() - suffix.length());
|
||||
else
|
||||
return str;
|
||||
public static String removePrefix(String str, String... prefixes) {
|
||||
for (String prefix : prefixes)
|
||||
if (str.startsWith(prefix))
|
||||
return str.substring(prefix.length());
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove one suffix of the suffixes of the string.
|
||||
*/
|
||||
public static String removeSuffix(String str, String... suffixes) {
|
||||
for (String suffix : suffixes)
|
||||
if (str.endsWith(suffix))
|
||||
return str.substring(0, str.length() - suffix.length());
|
||||
return str;
|
||||
}
|
||||
|
||||
public static boolean containsOne(Collection<String> patterns, String... targets) {
|
||||
|
142
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Unzipper.java
Normal file
142
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Unzipper.java
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
public class Unzipper {
|
||||
private final Path zipFile, dest;
|
||||
private boolean replaceExistentFile = false;
|
||||
private boolean terminateIfSubDirectoryNotExists = false;
|
||||
private String subDirectory = "/";
|
||||
private FileFilter filter = null;
|
||||
private String encoding;
|
||||
|
||||
/**
|
||||
* Decompress the given zip file to a directory.
|
||||
*
|
||||
* @param zipFile the input zip file to be uncompressed
|
||||
* @param destDir the dest directory to hold uncompressed files
|
||||
*/
|
||||
public Unzipper(Path zipFile, Path destDir) {
|
||||
this.zipFile = zipFile;
|
||||
this.dest = destDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress the given zip file to a directory.
|
||||
*
|
||||
* @param zipFile the input zip file to be uncompressed
|
||||
* @param destDir the dest directory to hold uncompressed files
|
||||
*/
|
||||
public Unzipper(File zipFile, File destDir) {
|
||||
this(zipFile.toPath(), destDir.toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* True if replace the existent files in destination directory,
|
||||
* otherwise those conflict files will be ignored.
|
||||
*/
|
||||
public Unzipper setReplaceExistentFile(boolean replaceExistentFile) {
|
||||
this.replaceExistentFile = replaceExistentFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called for every entry in the zip file.
|
||||
* Callback returns false if you want leave the specific file uncompressed.
|
||||
*/
|
||||
public Unzipper setFilter(FileFilter filter) {
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will only uncompress files in the "subDirectory", their path will be also affected.
|
||||
*
|
||||
* For example, if you set subDirectory to /META-INF, files in /META-INF/ will be
|
||||
* uncompressed to the destination directory without creating META-INF folder.
|
||||
*
|
||||
* Default value: "/"
|
||||
*/
|
||||
public Unzipper setSubDirectory(String subDirectory) {
|
||||
this.subDirectory = FileUtils.normalizePath(subDirectory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Unzipper setEncoding(String encoding) {
|
||||
this.encoding = encoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Unzipper setTerminateIfSubDirectoryNotExists() {
|
||||
this.terminateIfSubDirectoryNotExists = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress the given zip file to a directory.
|
||||
*
|
||||
* @throws IOException if zip file is malformed or filesystem error.
|
||||
*/
|
||||
public void unzip() throws IOException {
|
||||
Files.createDirectories(dest);
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(zipFile, encoding)) {
|
||||
Path root = fs.getPath(subDirectory);
|
||||
if (!root.isAbsolute() || (subDirectory.length() > 1 && subDirectory.endsWith("/")))
|
||||
throw new IllegalArgumentException("Subdirectory for unzipper must be absolute");
|
||||
|
||||
if (terminateIfSubDirectoryNotExists && Files.notExists(root))
|
||||
return;
|
||||
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file,
|
||||
BasicFileAttributes attrs) throws IOException {
|
||||
String relativePath = root.relativize(file).toString();
|
||||
Path destFile = dest.resolve(relativePath);
|
||||
if (filter != null && !filter.accept(file, false, destFile, relativePath))
|
||||
return FileVisitResult.CONTINUE;
|
||||
if (replaceExistentFile || Files.notExists(destFile))
|
||||
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir,
|
||||
BasicFileAttributes attrs) throws IOException {
|
||||
String relativePath = root.relativize(dir).toString();
|
||||
Path dirToCreate = dest.resolve(relativePath);
|
||||
if (filter != null && !filter.accept(dir, true, dirToCreate, relativePath))
|
||||
return FileVisitResult.CONTINUE;
|
||||
if (Files.notExists(dirToCreate)) {
|
||||
Files.createDirectory(dirToCreate);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public interface FileFilter {
|
||||
boolean accept(Path destPath, boolean isDirectory, Path zipEntry, String entryPath) throws IOException;
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.util;
|
||||
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* Non thread-safe
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ZipEngine implements Closeable {
|
||||
|
||||
private final byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
ZipOutputStream zos;
|
||||
|
||||
public ZipEngine(File f) throws IOException {
|
||||
zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
zos.closeEntry();
|
||||
zos.close();
|
||||
}
|
||||
|
||||
public void putDirectory(File sourceDir) throws IOException {
|
||||
putDirectory(sourceDir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress all the files in sourceDir
|
||||
*
|
||||
* @param sourceDir the directory to be compressed.
|
||||
* @param pathNameCallback callback(pathName, isDirectory) returns your
|
||||
* modified pathName
|
||||
*
|
||||
* @throws java.io.IOException if unable to compress or read files.
|
||||
*/
|
||||
public void putDirectory(File sourceDir, BiFunction<String, Boolean, String> pathNameCallback) throws IOException {
|
||||
putDirectoryImpl(sourceDir, sourceDir.isDirectory() ? sourceDir.getPath() : sourceDir.getParent(), pathNameCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress all the files in sourceDir
|
||||
*
|
||||
* @param source the file in basePath to be compressed
|
||||
* @param basePath the root directory to be compressed.
|
||||
* @param pathNameCallback callback(pathName, isDirectory) returns your
|
||||
* modified pathName, null if you dont want this file zipped
|
||||
*/
|
||||
private void putDirectoryImpl(File source, String basePath, BiFunction<String, Boolean, String> pathNameCallback) throws IOException {
|
||||
File[] files = null;
|
||||
if (source.isDirectory())
|
||||
files = source.listFiles();
|
||||
else if (source.isFile())
|
||||
files = new File[] { source };
|
||||
|
||||
if (files == null)
|
||||
return;
|
||||
String pathName; // the relative path (relative to basePath)
|
||||
for (File file : files)
|
||||
if (file.isDirectory()) {
|
||||
pathName = file.getPath().substring(basePath.length() + 1)
|
||||
+ "/";
|
||||
pathName = pathName.replace('\\', '/');
|
||||
if (pathNameCallback != null)
|
||||
pathName = pathNameCallback.apply(pathName, true);
|
||||
if (pathName == null)
|
||||
continue;
|
||||
put(new ZipEntry(pathName));
|
||||
putDirectoryImpl(file, basePath, pathNameCallback);
|
||||
} else {
|
||||
if (".DS_Store".equals(file.getName())) // For Mac computers.
|
||||
continue;
|
||||
pathName = file.getPath().substring(basePath.length() + 1);
|
||||
pathName = pathName.replace('\\', '/');
|
||||
if (pathNameCallback != null)
|
||||
pathName = pathNameCallback.apply(pathName, false);
|
||||
if (pathName == null)
|
||||
continue;
|
||||
putFile(file, pathName);
|
||||
}
|
||||
}
|
||||
|
||||
public void putFile(File file, String pathName) throws IOException {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
putStream(fis, pathName);
|
||||
}
|
||||
}
|
||||
|
||||
public void putStream(InputStream is, String pathName) throws IOException {
|
||||
put(new ZipEntry(pathName));
|
||||
IOUtils.copyTo(is, zos, buf);
|
||||
}
|
||||
|
||||
public void putTextFile(String text, String pathName) throws IOException {
|
||||
putTextFile(text, "UTF-8", pathName);
|
||||
}
|
||||
|
||||
public void putTextFile(String text, String encoding, String pathName) throws IOException {
|
||||
putStream(new ByteArrayInputStream(text.getBytes(encoding)), pathName);
|
||||
}
|
||||
|
||||
private final HashSet<String> names = new HashSet<>();
|
||||
|
||||
public void put(ZipEntry entry) throws IOException {
|
||||
if (names.add(entry.getName()))
|
||||
zos.putNextEntry(entry);
|
||||
}
|
||||
|
||||
}
|
123
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Zipper.java
Normal file
123
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Zipper.java
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.util;
|
||||
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Non thread-safe
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Zipper implements Closeable {
|
||||
|
||||
private final FileSystem fs;
|
||||
|
||||
public Zipper(Path zipFile) throws IOException {
|
||||
this(zipFile, null);
|
||||
}
|
||||
|
||||
public Zipper(Path zipFile, String encoding) throws IOException {
|
||||
Files.deleteIfExists(zipFile);
|
||||
fs = CompressingUtils.createWritableZipFileSystem(zipFile, encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
fs.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress all the files in sourceDir
|
||||
*
|
||||
* @param source the file in basePath to be compressed
|
||||
* @param rootDir the path of the directory in this zip file.
|
||||
*/
|
||||
public void putDirectory(Path source, String rootDir) throws IOException {
|
||||
putDirectory(source, rootDir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress all the files in sourceDir
|
||||
*
|
||||
* @param source the file in basePath to be compressed
|
||||
* @param targetDir the path of the directory in this zip file.
|
||||
* @param filter returns false if you do not want that file or directory
|
||||
*/
|
||||
public void putDirectory(Path source, String targetDir, Predicate<String> filter) throws IOException {
|
||||
File[] files = null;
|
||||
|
||||
Path root = fs.getPath(targetDir);
|
||||
Files.createDirectories(root);
|
||||
Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (".DS_Store".equals(file.getFileName().toString())) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
String relativePath = source.relativize(file).normalize().toString();
|
||||
if (filter != null && !filter.test(relativePath)) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
Files.copy(file, root.resolve(relativePath));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
String relativePath = source.relativize(dir).normalize().toString();
|
||||
if (filter != null && !filter.test(relativePath)) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
Path path = root.resolve(relativePath);
|
||||
if (Files.notExists(path)) {
|
||||
Files.createDirectory(path);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void putFile(File file, String path) throws IOException {
|
||||
putFile(file.toPath(), path);
|
||||
}
|
||||
|
||||
public void putFile(Path file, String path) throws IOException {
|
||||
Files.copy(file, fs.getPath(path));
|
||||
}
|
||||
|
||||
public void putStream(InputStream in, String path) throws IOException {
|
||||
Files.copy(in, fs.getPath(path));
|
||||
}
|
||||
|
||||
public void putTextFile(String text, String path) throws IOException {
|
||||
putTextFile(text, "UTF-8", path);
|
||||
}
|
||||
|
||||
public void putTextFile(String text, String encoding, String pathName) throws IOException {
|
||||
Files.write(fs.getPath(pathName), text.getBytes(encoding));
|
||||
}
|
||||
|
||||
}
|
@ -39,7 +39,6 @@ allprojects {
|
||||
|
||||
dependencies {
|
||||
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
|
||||
compile group: 'org.apache.commons', name: 'commons-compress', version: '1.17'
|
||||
compile group: 'org.tukaani', name: 'xz', version: '1.8'
|
||||
compile group: 'org.hildan.fxgson', name: 'fx-gson', version: '3.1.0'
|
||||
compile group: 'org.jenkins-ci', name: 'constant-pool-scanner', version: '1.2'
|
||||
|
Loading…
x
Reference in New Issue
Block a user