Use ZipFileSystem instead of commons-compress

This commit is contained in:
huanghongxun 2018-08-07 11:52:01 +08:00
parent cadafe13e1
commit 51afcf2dee
24 changed files with 487 additions and 510 deletions

View File

@ -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";
}

View File

@ -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";

View File

@ -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
);

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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())

View File

@ -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"))

View File

@ -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;

View File

@ -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) {

View File

@ -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);
}

View File

@ -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()) {

View File

@ -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())

View File

@ -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)));

View File

@ -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()))

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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";

View File

@ -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 {

View File

@ -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");

View File

@ -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) {

View 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;
}
}

View File

@ -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);
}
}

View 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));
}
}

View File

@ -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'