feat: custom zipfilesystem.

This commit is contained in:
huanghongxun 2021-10-24 17:21:47 +08:00
parent ec37b32eaa
commit 9706a799be
21 changed files with 1276 additions and 100 deletions

View File

@ -23,12 +23,12 @@ import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.FileSystem;
/**
* @author huangyuhui
@ -40,20 +40,20 @@ public final class HMCLModpackManager {
/**
* Read the manifest in a HMCL modpack.
*
* @param file a HMCL modpack file.
* @param fs a HMCL modpack file.
* @param encoding encoding of modpack zip file.
* @return the manifest of HMCL modpack.
* @throws IOException if the file is not a valid zip file.
* @throws JsonParseException if the manifest.json is missing or malformed.
*/
public static Modpack readHMCLModpackManifest(Path file, Charset encoding) throws IOException, JsonParseException {
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json", encoding);
public static Modpack readHMCLModpackManifest(FileSystem fs, Charset encoding) throws IOException, JsonParseException {
String manifestJson = FileUtils.readText(fs.getPath("modpack.json"));
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, HMCLModpack.class).setEncoding(encoding);
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json", encoding);
String gameJson = FileUtils.readText(fs.getPath("minecraft/pack.json"));
Version game = JsonUtils.fromNonNullJson(gameJson, Version.class);
if (game.getJar() == null)
if (StringUtils.isBlank(manifest.getVersion()))
throw new JsonParseException("Cannot recognize the game version of modpack " + file + ".");
throw new JsonParseException("Cannot recognize the game version of modpack");
else
manifest.setManifest(HMCLModpackManifest.INSTANCE);
else

View File

@ -41,6 +41,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.ZipFileSystem;
import java.io.File;
import java.io.FileNotFoundException;
@ -61,34 +62,38 @@ public final class ModpackHelper {
private ModpackHelper() {}
public static Modpack readModpackManifest(Path file, Charset charset) throws UnsupportedModpackException, ManuallyCreatedModpackException {
try {
return McbbsModpackManifest.readManifest(file, charset);
} catch (Exception ignored) {
// ignore it, not a valid MCBBS modpack.
}
try (ZipFileSystem zfs = CompressingUtils.createReadOnlyZipFileSystem(file, charset)) {
try {
return McbbsModpackManifest.readManifest(zfs, charset);
} catch (Exception ignored) {
// ignore it, not a valid MCBBS modpack.
}
try {
return CurseManifest.readCurseForgeModpackManifest(file, charset);
} catch (Exception e) {
// ignore it, not a valid CurseForge modpack.
}
try {
return CurseManifest.readCurseForgeModpackManifest(zfs, charset);
} catch (Exception e) {
// ignore it, not a valid CurseForge modpack.
}
try {
return HMCLModpackManager.readHMCLModpackManifest(file, charset);
} catch (Exception e) {
// ignore it, not a valid HMCL modpack.
}
try {
return HMCLModpackManager.readHMCLModpackManifest(zfs, charset);
} catch (Exception e) {
// ignore it, not a valid HMCL modpack.
}
try {
return MultiMCInstanceConfiguration.readMultiMCModpackManifest(file, charset);
} catch (Exception e) {
// ignore it, not a valid MultiMC modpack.
}
try {
return MultiMCInstanceConfiguration.readMultiMCModpackManifest(zfs, file, charset);
} catch (Exception e) {
// ignore it, not a valid MultiMC modpack.
}
try {
return ServerModpackManifest.readManifest(file, charset);
} catch (Exception e) {
// ignore it, not a valid Server modpack.
try {
return ServerModpackManifest.readManifest(zfs, charset);
} catch (Exception e) {
// ignore it, not a valid Server modpack.
}
} catch (IOException ignored) {
}
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(file, charset)) {

View File

@ -19,6 +19,7 @@ package org.jackhuang.hmcl.upgrade;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.util.Pack200Utils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.tukaani.xz.XZInputStream;
@ -49,7 +50,7 @@ class HMCLDownloadTask extends FileDownloadTask {
break;
case PACK_XZ:
byte[] raw = Files.readAllBytes(target);
byte[] raw = FileUtils.readAllBytes(target);
try (InputStream in = new XZInputStream(new ByteArrayInputStream(raw));
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
Pack200Utils.unpack(in, out);

View File

@ -103,7 +103,7 @@ public class LibraryDownloadTask extends Task<Void> {
else
throw new LibraryDownloadException(library, t);
} else {
if (xz) unpackLibrary(jar, Files.readAllBytes(xzFile.toPath()));
if (xz) unpackLibrary(jar, FileUtils.readAllBytes(xzFile.toPath()));
}
}
@ -180,7 +180,7 @@ public class LibraryDownloadTask extends Task<Void> {
if (checksums == null || checksums.isEmpty()) {
return true;
}
byte[] fileData = Files.readAllBytes(libPath.toPath());
byte[] fileData = FileUtils.readAllBytes(libPath.toPath());
boolean valid = checksums.contains(encodeHex(digest("SHA-1", fileData)));
if (!valid && libPath.getName().endsWith(".jar")) {
valid = validateJar(fileData, checksums);

View File

@ -230,7 +230,7 @@ public final class OptiFineInstallTask extends Task<Version> {
Path configClass = fs.getPath("Config.class");
if (!Files.exists(configClass)) configClass = fs.getPath("net/optifine/Config.class");
if (!Files.exists(configClass)) throw new IOException("Unrecognized installer");
ConstantPool pool = ConstantPoolScanner.parse(Files.readAllBytes(configClass), ConstantType.UTF8);
ConstantPool pool = ConstantPoolScanner.parse(FileUtils.readAllBytes(configClass), ConstantType.UTF8);
List<String> constants = new ArrayList<>();
pool.list(Utf8Constant.class).forEach(utf8 -> constants.add(utf8.get()));
String mcVersion = getOrDefault(constants, constants.indexOf("MC_VERSION") + 1, null);

View File

@ -104,13 +104,13 @@ public final class GameVersion {
Path minecraft = gameJar.getPath("net/minecraft/client/Minecraft.class");
if (Files.exists(minecraft)) {
Optional<String> result = getVersionOfClassMinecraft(Files.readAllBytes(minecraft));
Optional<String> result = getVersionOfClassMinecraft(FileUtils.readAllBytes(minecraft));
if (result.isPresent())
return result;
}
Path minecraftServer = gameJar.getPath("net/minecraft/server/MinecraftServer.class");
if (Files.exists(minecraftServer))
return getVersionFromClassMinecraftServer(Files.readAllBytes(minecraftServer));
return getVersionFromClassMinecraftServer(FileUtils.readAllBytes(minecraftServer));
return Optional.empty();
} catch (IOException e) {
return Optional.empty();

View File

@ -23,13 +23,14 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.ZipFileSystem;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
@ -121,11 +122,11 @@ public final class CurseManifest {
* @throws JsonParseException if the manifest.json is missing or malformed.
* @return the manifest.
*/
public static Modpack readCurseForgeModpackManifest(Path zip, Charset encoding) throws IOException, JsonParseException {
String json = CompressingUtils.readTextZipEntry(zip, "manifest.json", encoding);
public static Modpack readCurseForgeModpackManifest(ZipFileSystem zip, Charset encoding) throws IOException, JsonParseException {
String json = FileUtils.readText(zip.getPath("manifest.json"));
CurseManifest manifest = JsonUtils.fromNonNullJson(json, CurseManifest.class);
return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(),
CompressingUtils.readTextZipEntryQuietly(zip, "modlist.html", encoding).orElse( "No description"), encoding, manifest) {
Lang.ignoringException(() -> FileUtils.readText(zip.getPath("modlist.html")), "No description"), encoding, manifest) {
@Override
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
return new CurseInstallTask(dependencyManager, zipFile, this, manifest, name);

View File

@ -25,7 +25,6 @@ import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.*;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.Nullable;
@ -33,7 +32,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
@ -435,29 +433,26 @@ public class McbbsModpackManifest implements Validation {
}
private static Modpack fromManifestFile(Path manifestFile, Charset encoding) throws IOException, JsonParseException {
String json = FileUtils.readText(manifestFile, StandardCharsets.UTF_8);
McbbsModpackManifest manifest = JsonUtils.fromNonNullJson(json, McbbsModpackManifest.class);
McbbsModpackManifest manifest = JsonUtils.fromNonNullJson(FileUtils.readText(manifestFile), McbbsModpackManifest.class);
return manifest.toModpack(encoding);
}
/**
* @param zip the MCBBS modpack file.
* @param fs the MCBBS modpack file.
* @param encoding the modpack zip file encoding.
* @throws IOException if the file is not a valid zip file.
* @throws JsonParseException if the server-manifest.json is missing or malformed.
* @return the manifest.
* @throws IOException if the file is not a valid zip file.
* @throws JsonParseException if the server-manifest.json is missing or malformed.
*/
public static Modpack readManifest(Path zip, Charset encoding) throws IOException, JsonParseException {
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(zip, encoding)) {
Path mcbbsPackMeta = fs.getPath("mcbbs.packmeta");
if (Files.exists(mcbbsPackMeta)) {
return fromManifestFile(mcbbsPackMeta, encoding);
}
Path manifestJson = fs.getPath("manifest.json");
if (Files.exists(manifestJson)) {
return fromManifestFile(manifestJson, encoding);
}
throw new IOException("`mcbbs.packmeta` or `manifest.json` cannot be found");
public static Modpack readManifest(FileSystem fs, Charset encoding) throws IOException, JsonParseException {
Path mcbbsPackMeta = fs.getPath("mcbbs.packmeta");
if (Files.exists(mcbbsPackMeta)) {
return fromManifestFile(mcbbsPackMeta, encoding);
}
Path manifestJson = fs.getPath("manifest.json");
if (Files.exists(manifestJson)) {
return fromManifestFile(manifestJson, encoding);
}
throw new IOException("`mcbbs.packmeta` or `manifest.json` cannot be found");
}
}

View File

@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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
@ -21,8 +21,8 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.ZipFileSystem;
import java.io.File;
import java.io.IOException;
@ -30,7 +30,6 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
@ -348,24 +347,22 @@ public final class MultiMCInstanceConfiguration {
}
}
public static Modpack readMultiMCModpackManifest(Path modpackFile, Charset encoding) throws IOException {
try (FileSystem fs = CompressingUtils.readonly(modpackFile).setEncoding(encoding).build()) {
Path root = getRootPath(fs.getPath("/"));
MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(root);
String name = FileUtils.getName(root, FileUtils.getNameWithoutExtension(modpackFile));
public static Modpack readMultiMCModpackManifest(ZipFileSystem zipFile, Path filePath, Charset encoding) throws IOException {
Path root = getRootPath(zipFile.getPath("/"));
MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(root);
String name = FileUtils.getNameWithoutExtension(filePath);
Path instancePath = root.resolve("instance.cfg");
if (Files.notExists(instancePath))
throw new IOException("`instance.cfg` not found, " + modpackFile + " is not a valid MultiMC modpack.");
try (InputStream instanceStream = Files.newInputStream(instancePath)) {
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, instanceStream, manifest);
return new Modpack(cfg.getName(), "", "", cfg.getGameVersion(), cfg.getNotes(), encoding, cfg) {
@Override
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
return new MultiMCModpackInstallTask(dependencyManager, zipFile, this, cfg, name);
}
};
}
Path instancePath = root.resolve("instance.cfg");
if (Files.notExists(instancePath))
throw new IOException("`instance.cfg` not found, " + filePath + " is not a valid MultiMC modpack.");
try (InputStream instanceStream = Files.newInputStream(instancePath)) {
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, instanceStream, manifest);
return new Modpack(cfg.getName(), "", "", cfg.getGameVersion(), cfg.getNotes(), encoding, cfg) {
@Override
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
return new MultiMCModpackInstallTask(dependencyManager, zipFile, this, cfg, name);
}
};
}
}
}

View File

@ -32,6 +32,7 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.ZipFileSystem;
import java.io.File;
import java.io.IOException;
@ -144,11 +145,11 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
public void execute() throws Exception {
Version version = repository.readVersionJson(name);
try (FileSystem fs = CompressingUtils.readonly(zipFile.toPath()).setAutoDetectEncoding(true).build()) {
try (ZipFileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(zipFile.toPath())) {
Path root = MultiMCInstanceConfiguration.getRootPath(fs.getPath("/"));
Path patches = root.resolve("patches");
if (Files.exists(patches)) {
if (Files.isDirectory(patches)) {
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(patches)) {
for (Path patchJson : directoryStream) {
if (patchJson.toString().endsWith(".json")) {

View File

@ -25,12 +25,12 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
import org.jackhuang.hmcl.util.gson.Validation;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.FileSystem;
import java.util.Collections;
import java.util.List;
@ -129,13 +129,13 @@ public class ServerModpackManifest implements Validation {
}
/**
* @param zip the CurseForge modpack file.
* @throws IOException if the file is not a valid zip file.
* @throws JsonParseException if the server-manifest.json is missing or malformed.
* @param fs the CurseForge modpack file.
* @return the manifest.
* @throws IOException if the file is not a valid zip file.
* @throws JsonParseException if the server-manifest.json is missing or malformed.
*/
public static Modpack readManifest(Path zip, Charset encoding) throws IOException, JsonParseException {
String json = CompressingUtils.readTextZipEntry(zip, "server-manifest.json", encoding);
public static Modpack readManifest(FileSystem fs, Charset encoding) throws IOException, JsonParseException {
String json = FileUtils.readText(fs.getPath("server-manifest.json"));
ServerModpackManifest manifest = JsonUtils.fromNonNullJson(json, ServerModpackManifest.class);
return manifest.toModpack(encoding);
}

View File

@ -42,6 +42,7 @@ import java.util.zip.ZipException;
* @author huangyuhui
*/
public final class CompressingUtils {
private static final ZipFileSystemProvider MY_ZIPFS_PROVIDER = new ZipFileSystemProvider();
private static final FileSystemProvider ZIPFS_PROVIDER = FileSystemProvider.installedProviders().stream()
.filter(it -> "jar".equalsIgnoreCase(it.getScheme()))
@ -153,12 +154,12 @@ public final class CompressingUtils {
return new Builder(zipFile, true).setUseTempFile(true);
}
public static FileSystem createReadOnlyZipFileSystem(Path zipFile) throws IOException {
return createReadOnlyZipFileSystem(zipFile, null);
public static ZipFileSystem createReadOnlyZipFileSystem(Path zipFile) throws IOException {
return new ZipFileSystem(MY_ZIPFS_PROVIDER, new ZipFile(zipFile.toFile()), true);
}
public static FileSystem createReadOnlyZipFileSystem(Path zipFile, Charset charset) throws IOException {
return createZipFileSystem(zipFile, false, false, charset);
public static ZipFileSystem createReadOnlyZipFileSystem(Path zipFile, Charset charset) throws IOException {
return new ZipFileSystem(MY_ZIPFS_PROVIDER, new ZipFile(zipFile.toFile(), charset.name()), true);
}
public static FileSystem createWritableZipFileSystem(Path zipFile) throws IOException {
@ -199,7 +200,7 @@ public final class CompressingUtils {
*/
public static String readTextZipEntry(File zipFile, String name) throws IOException {
try (ZipFile s = new ZipFile(zipFile)) {
return IOUtils.readFullyAsString(s.getInputStream(s.getEntry(name)), StandardCharsets.UTF_8);
return readTextZipEntry(s, name);
}
}
@ -208,15 +209,20 @@ public final class CompressingUtils {
*
* @param zipFile the zip file
* @param name the location of the text in zip file, something like A/B/C/D.txt
* @param encoding encoding of zip file.
* @throws IOException if the file is not a valid zip file.
* @return the plain text content of given file.
*/
public static String readTextZipEntry(Path zipFile, String name, Charset encoding) throws IOException {
try (ZipFile s = new ZipFile(zipFile.toFile(), encoding.name())) {
return IOUtils.readFullyAsString(s.getInputStream(s.getEntry(name)), StandardCharsets.UTF_8);
return readTextZipEntry(s, name);
}
}
public static String readTextZipEntry(ZipFile s, String name) throws IOException {
return IOUtils.readFullyAsString(s.getInputStream(s.getEntry(name)), StandardCharsets.UTF_8);
}
/**
* Read the text content of a file in zip.
*
@ -224,7 +230,7 @@ public final class CompressingUtils {
* @param name the location of the text in zip file, something like A/B/C/D.txt
* @return the plain text content of given file.
*/
public static Optional<String> readTextZipEntryQuietly(File file, String name) {
public static Optional<String> readTextZipEntryQuietly(ZipFile file, String name) {
try {
return Optional.of(readTextZipEntry(file, name));
} catch (IOException e) {

View File

@ -0,0 +1,159 @@
package org.jackhuang.hmcl.util.io;
import java.util.regex.PatternSyntaxException;
public final class FileSystemUtils {
private FileSystemUtils() {
}
private static char EOL = 0;
private static boolean isRegexMeta(char var0) {
return ".^$+{[]|()".indexOf(var0) != -1;
}
private static boolean isGlobMeta(char var0) {
return "\\*?[{".indexOf(var0) != -1;
}
private static char next(String var0, int var1) {
return var1 < var0.length() ? var0.charAt(var1) : EOL;
}
public static String toRegexPattern(String var0) {
boolean var1 = false;
StringBuilder var2 = new StringBuilder("^");
int var3 = 0;
while(true) {
while(var3 < var0.length()) {
char var4 = var0.charAt(var3++);
switch(var4) {
case '*':
if (next(var0, var3) == '*') {
var2.append(".*");
++var3;
} else {
var2.append("[^/]*");
}
break;
case ',':
if (var1) {
var2.append(")|(?:");
} else {
var2.append(',');
}
break;
case '/':
var2.append(var4);
break;
case '?':
var2.append("[^/]");
break;
case '[':
var2.append("[[^/]&&[");
if (next(var0, var3) == '^') {
var2.append("\\^");
++var3;
} else {
if (next(var0, var3) == '!') {
var2.append('^');
++var3;
}
if (next(var0, var3) == '-') {
var2.append('-');
++var3;
}
}
boolean var6 = false;
char var7 = 0;
while(var3 < var0.length()) {
var4 = var0.charAt(var3++);
if (var4 == ']') {
break;
}
if (var4 == '/') {
throw new PatternSyntaxException("Explicit 'name separator' in class", var0, var3 - 1);
}
if (var4 == '\\' || var4 == '[' || var4 == '&' && next(var0, var3) == '&') {
var2.append('\\');
}
var2.append(var4);
if (var4 == '-') {
if (!var6) {
throw new PatternSyntaxException("Invalid range", var0, var3 - 1);
}
if ((var4 = next(var0, var3++)) == EOL || var4 == ']') {
break;
}
if (var4 < var7) {
throw new PatternSyntaxException("Invalid range", var0, var3 - 3);
}
var2.append(var4);
var6 = false;
} else {
var6 = true;
var7 = var4;
}
}
if (var4 != ']') {
throw new PatternSyntaxException("Missing ']", var0, var3 - 1);
}
var2.append("]]");
break;
case '\\':
if (var3 == var0.length()) {
throw new PatternSyntaxException("No character to escape", var0, var3 - 1);
}
char var5 = var0.charAt(var3++);
if (isGlobMeta(var5) || isRegexMeta(var5)) {
var2.append('\\');
}
var2.append(var5);
break;
case '{':
if (var1) {
throw new PatternSyntaxException("Cannot nest groups", var0, var3 - 1);
}
var2.append("(?:(?:");
var1 = true;
break;
case '}':
if (var1) {
var2.append("))");
var1 = false;
} else {
var2.append('}');
}
break;
default:
if (isRegexMeta(var4)) {
var2.append('\\');
}
var2.append(var4);
}
}
if (var1) {
throw new PatternSyntaxException("Missing '}", var0, var3 - 1);
}
return var2.append('$').toString();
}
}
}

View File

@ -113,11 +113,11 @@ public final class FileUtils {
}
public static String readText(File file) throws IOException {
return readText(file, UTF_8);
return readText(file.toPath());
}
public static String readText(File file, Charset charset) throws IOException {
return new String(Files.readAllBytes(file.toPath()), charset);
return readText(file.toPath(), charset);
}
public static String readText(Path file) throws IOException {
@ -125,7 +125,11 @@ public final class FileUtils {
}
public static String readText(Path file, Charset charset) throws IOException {
return new String(Files.readAllBytes(file), charset);
return IOUtils.readFullyAsString(Files.newInputStream(file), charset);
}
public static byte[] readAllBytes(Path file) throws IOException {
return IOUtils.readFullyAsByteArray(Files.newInputStream(file));
}
/**

View File

@ -101,7 +101,7 @@ public class Unzipper {
*/
public void unzip() throws IOException {
Files.createDirectories(dest);
try (FileSystem fs = CompressingUtils.readonly(zipFile).setEncoding(encoding).setAutoDetectEncoding(true).build()) {
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");

View File

@ -0,0 +1,81 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.io;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
public class ZipFileAttributes implements BasicFileAttributes {
private final long size;
private final boolean symbolicLink;
private final boolean regularFile;
private final boolean directory;
public ZipFileAttributes(long size, boolean symbolicLink, boolean regularFile, boolean directory) {
this.size = size;
this.symbolicLink = symbolicLink;
this.regularFile = regularFile;
this.directory = directory;
}
@Override
public FileTime lastModifiedTime() {
return null;
}
@Override
public FileTime lastAccessTime() {
return null;
}
@Override
public FileTime creationTime() {
return null;
}
@Override
public boolean isRegularFile() {
return regularFile;
}
@Override
public boolean isDirectory() {
return directory;
}
@Override
public boolean isSymbolicLink() {
return symbolicLink;
}
@Override
public boolean isOther() {
return false;
}
@Override
public long size() {
return size;
}
@Override
public Object fileKey() {
return null;
}
}

View File

@ -0,0 +1,310 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.io;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.nio.file.spi.FileSystemProvider;
import java.util.*;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.util.Lang.toIterable;
import static org.jackhuang.hmcl.util.io.ZipPath.getPathComponents;
public class ZipFileSystem extends FileSystem {
private final ZipFileSystemProvider provider;
private final ZipFile zipFile;
private final boolean readOnly;
private final IndexNode root;
private final Map<String, IndexNode> entries = new HashMap<>();
final ZipPath rootDir;
private volatile boolean isOpen = true;
public ZipFileSystem(ZipFileSystemProvider provider, ZipFile zipFile, boolean readOnly) {
this.provider = provider;
this.zipFile = zipFile;
this.readOnly = readOnly;
this.root = new IndexNode(null, true, "");
this.rootDir = new ZipPath(this, "/");
buildTree();
}
@Override
public FileSystemProvider provider() {
return provider;
}
@Override
public void close() throws IOException {
isOpen = false;
zipFile.close();
}
@Override
public boolean isOpen() {
return isOpen;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public String getSeparator() {
return "/";
}
@Override
public Iterable<Path> getRootDirectories() {
return Collections.singleton(rootDir);
}
@Override
public Iterable<FileStore> getFileStores() {
return null;
}
@Override
public Set<String> supportedFileAttributeViews() {
return null;
}
@NotNull
@Override
public Path getPath(@NotNull String first, @NotNull String @NotNull ... more) {
StringBuilder sb = new StringBuilder(first);
for (String segment : more) {
if (segment.length() > 0) {
if (sb.length() > 0) {
sb.append('/');
}
sb.append(segment);
}
}
return new ZipPath(this, sb.toString());
}
ZipFileAttributes readAttributes(ZipPath path) {
ensureOpen();
Optional<IndexNode> inode = getInode(path);
if (!inode.isPresent()) return null;
return inode.get().getAttributes();
}
InputStream newInputStream(ZipPath path, OpenOption... options) throws IOException {
ensureOpen();
ZipPath realPath = path.toRealPath();
ZipArchiveEntry entry = zipFile.getEntry(realPath.getEntryName());
return zipFile.getInputStream(entry);
}
DirectoryStream<Path> newDirectoryStream(ZipPath dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
Optional<IndexNode> inode = getInode(dir);
if (!inode.isPresent() || !inode.get().isDirectory()) throw new NotDirectoryException(dir.toString());
List<ZipPath> list = new ArrayList<>();
for (IndexNode child = inode.get().child; child != null; child = child.sibling) {
list.add(new ZipPath(this, child.name));
}
return new DirectoryStream<Path>() {
private volatile boolean isClosed = false;
private volatile Iterator<ZipPath> itr;
@Override
public synchronized Iterator<Path> iterator() {
if (isClosed)
throw new ClosedDirectoryStreamException();
if (itr != null)
throw new IllegalStateException("Iterator has already been returned");
itr = list.iterator();
return new Iterator<Path>() {
@Override
public boolean hasNext() {
if (isClosed) return false;
return itr.hasNext();
}
@Override
public Path next() {
if (isClosed) throw new NoSuchElementException();
return itr.next();
}
};
}
@Override
public synchronized void close() {
isClosed = true;
}
};
}
void checkAccess(ZipPath path) throws IOException {
ensureOpen();
if (!getInode(path.getEntryName()).isPresent()) {
throw new NoSuchFileException(path.toString());
}
}
private static final String GLOB_SYNTAX = "glob";
private static final String REGEX_SYNTAX = "regex";
@Override
public PathMatcher getPathMatcher(String syntaxAndInput) {
int pos = syntaxAndInput.indexOf(':');
if (pos <= 0 || pos == syntaxAndInput.length()) {
throw new IllegalArgumentException();
}
String syntax = syntaxAndInput.substring(0, pos);
String input = syntaxAndInput.substring(pos + 1);
String expr;
if (syntax.equalsIgnoreCase(GLOB_SYNTAX)) {
expr = FileSystemUtils.toRegexPattern(input);
} else {
if (syntax.equalsIgnoreCase(REGEX_SYNTAX)) {
expr = input;
} else {
throw new UnsupportedOperationException("Syntax '" + syntax +
"' not recognized");
}
}
// return matcher
final Pattern pattern = Pattern.compile(expr);
return path -> pattern.matcher(path.toString()).matches();
}
@Override
public UserPrincipalLookupService getUserPrincipalLookupService() {
throw new UnsupportedOperationException();
}
@Override
public WatchService newWatchService() {
throw new UnsupportedOperationException();
}
private void ensureOpen() {
if (!isOpen) {
throw new ClosedFileSystemException();
}
}
private Optional<IndexNode> getInode(String entryName) {
return Optional.ofNullable(entries.get(entryName));
}
private Optional<IndexNode> getInode(ZipPath path) {
return getInode(path.toAbsolutePath().normalize().getEntryName());
}
private void buildTree() {
entries.put("", root);
for (ZipArchiveEntry entry : toIterable(zipFile.getEntriesInPhysicalOrder())) {
List<String> components = getPathComponents(entry.getName());
IndexNode node = new IndexNode(entry, entry.isDirectory(), String.join("/", components));
entries.put(node.name, node);
while (true) {
if (components.size() == 0) break;
if (components.size() == 1) {
node.sibling = root.child;
root.child = node;
break;
}
String parentName = String.join("/", components.subList(0, components.size() - 1));
if (entries.containsKey(parentName)) {
IndexNode parent = entries.get(parentName);
node.sibling = parent.child;
parent.child = node;
break;
}
// Add new pseudo directory entry
IndexNode parent = new IndexNode(null, true, parentName);
entries.put(parentName, parent);
node.sibling = parent.child;
parent.child = node;
node = parent;
}
}
}
private class IndexNode {
private final ZipArchiveEntry entry;
private final boolean isDirectory;
private final String name;
private ZipFileAttributes attributes;
public IndexNode(ZipArchiveEntry entry, boolean isDirectory, String name) {
this.entry = entry;
this.isDirectory = isDirectory;
this.name = name;
}
public boolean isDirectory() {
return isDirectory;
}
public String getName() {
return name;
}
public InputStream getInputStream() throws IOException {
if (entry == null) throw new IOException("Entry " + name + " cannot open");
return zipFile.getInputStream(entry);
}
public ZipFileAttributes getAttributes() {
if (attributes == null) {
if (entry == null) {
attributes = new ZipFileAttributes(0, false, false, true);
} else {
attributes = new ZipFileAttributes(
entry.getSize(),
entry.isUnixSymlink(),
!entry.isDirectory(),
entry.isDirectory()
);
}
}
return attributes;
}
IndexNode sibling;
IndexNode child;
}
}

View File

@ -0,0 +1,139 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.io;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Set;
import static org.jackhuang.hmcl.util.io.ZipPath.ensurePath;
public class ZipFileSystemProvider extends FileSystemProvider {
@Override
public String getScheme() {
return "zip";
}
@Override
public ZipFileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public FileSystem getFileSystem(URI uri) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Path getPath(@NotNull URI uri) {
throw new UnsupportedOperationException();
}
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public InputStream newInputStream(Path path, OpenOption... options) throws IOException {
ZipPath zipPath = ensurePath(path);
return zipPath.getFileSystem().newInputStream(zipPath, options);
}
@Override
public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
ZipPath zipPath = ensurePath(dir);
return zipPath.getFileSystem().newDirectoryStream(zipPath, filter);
}
@Override
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
throw new ReadOnlyFileSystemException();
}
@Override
public void delete(Path path) throws IOException {
throw new ReadOnlyFileSystemException();
}
@Override
public void copy(Path source, Path target, CopyOption... options) throws IOException {
throw new ReadOnlyFileSystemException();
}
@Override
public void move(Path source, Path target, CopyOption... options) throws IOException {
throw new ReadOnlyFileSystemException();
}
@Override
public boolean isSameFile(Path path, Path path2) throws IOException {
return path.toRealPath().equals(path2.toRealPath());
}
@Override
public boolean isHidden(Path path) throws IOException {
return false;
}
@Override
public FileStore getFileStore(Path path) throws IOException {
return null;
}
@Override
public void checkAccess(Path path, AccessMode... modes) throws IOException {
ZipPath zipPath = ensurePath(path);
zipPath.checkAccess(modes);
}
@Override
public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
return null;
}
@Override
public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
if (type == BasicFileAttributes.class || type == ZipFileAttributes.class) {
//noinspection unchecked
return (A) ensurePath(path).getAttributes();
}
return null;
}
@Override
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
throw new ReadOnlyFileSystemException();
}
}

View File

@ -0,0 +1,379 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.io;
import org.jackhuang.hmcl.util.Lang;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.util.*;
public class ZipPath implements Path {
private final ZipFileSystem zfs;
private final List<String> path;
private List<String> normalized;
private final boolean absolute;
ZipPath(ZipFileSystem zfs, String path) {
this(zfs, getPathComponents(path), path.startsWith("/"));
}
ZipPath(ZipFileSystem zfs, List<String> path, boolean absolute) {
this.zfs = zfs;
this.path = path;
this.absolute = absolute;
}
@NotNull
@Override
public ZipFileSystem getFileSystem() {
return zfs;
}
@Override
public boolean isAbsolute() {
return absolute;
}
@Override
public ZipPath getRoot() {
if (this.isAbsolute())
return zfs.rootDir;
else
return null;
}
@Override
public ZipPath getFileName() {
if (path.size() <= 1) return this;
else return new ZipPath(zfs, Collections.singletonList(path.get(path.size() - 1)), false);
}
@Override
public ZipPath getParent() {
if (path.isEmpty()) return null;
else if (path.size() == 1) return getRoot();
else return new ZipPath(zfs, path.subList(0, path.size() - 1), absolute);
}
@Override
public int getNameCount() {
return path.size();
}
@NotNull
@Override
public ZipPath getName(int index) {
if (index < 0 || index >= path.size()) throw new IllegalArgumentException();
return new ZipPath(zfs, Collections.singletonList(path.get(index)), false);
}
@NotNull
@Override
public ZipPath subpath(int beginIndex, int endIndex) {
if (beginIndex < 0 || beginIndex >= path.size() || endIndex > path.size() || beginIndex >= endIndex) {
throw new IllegalArgumentException();
}
return new ZipPath(zfs, path.subList(beginIndex, endIndex), absolute);
}
@Override
public boolean startsWith(@NotNull Path other) {
ZipPath p1 = this;
ZipPath p2 = ensurePath(other);
if (p1.isAbsolute() != p2.isAbsolute() || p1.path.size() < p2.path.size()) {
return false;
}
int length = p2.path.size();
for (int i = 0; i < length; i++) {
if (!Objects.equals(p1.path.get(i), p2.path.get(i))) {
return false;
}
}
return true;
}
@Override
public boolean startsWith(@NotNull String other) {
return startsWith(getFileSystem().getPath(other));
}
@Override
public boolean endsWith(@NotNull Path other) {
ZipPath p1 = this;
ZipPath p2 = ensurePath(other);
if (p2.isAbsolute() && !p1.isAbsolute() ||
p2.isAbsolute() && p1.isAbsolute() && p1.path.size() != p2.path.size() ||
p1.path.size() < p2.path.size()
) {
return false;
}
int length = p2.path.size();
for (int i = 0; i < length; i++) {
if (!Objects.equals(p1.path.get(p1.path.size() - i - 1), p2.path.get(p2.path.size() - i - 1))) {
return false;
}
}
return true;
}
@Override
public boolean endsWith(@NotNull String other) {
return endsWith(getFileSystem().getPath(other));
}
@NotNull
@Override
public ZipPath normalize() {
if (isNormalizable()) {
doNormalize();
return new ZipPath(zfs, normalized, absolute);
}
return this;
}
private boolean isNormalizable() {
for (String component : path) {
if (".".equals(component) || "..".equals(component)) {
return true;
}
}
return false;
}
private void doNormalize() {
if (normalized != null) return;
Stack<String> stack = new Stack<>();
for (String component : path) {
if (".".equals(component)) {
continue;
} else if ("..".equals(component)) {
if (!stack.isEmpty()) stack.pop();
} else {
stack.push(component);
}
}
normalized = new ArrayList<>(stack);
}
@NotNull
@Override
public ZipPath resolve(@NotNull Path other) {
ZipPath p1 = this;
ZipPath p2 = ensurePath(other);
if (p2.isAbsolute()) return p2;
return new ZipPath(zfs, Lang.merge(p1.path, p2.path), absolute);
}
@NotNull
@Override
public ZipPath resolve(@NotNull String other) {
return resolve(getFileSystem().getPath(other));
}
@NotNull
@Override
public ZipPath resolveSibling(@NotNull Path other) {
ZipPath parent = getParent();
return parent == null ? ensurePath(other) : parent.resolve(other);
}
@NotNull
@Override
public ZipPath resolveSibling(@NotNull String other) {
return resolveSibling(zfs.getPath(other));
}
@NotNull
@Override
public Path relativize(@NotNull Path other) {
ZipPath p1 = this;
ZipPath p2 = ensurePath(other);
if (p2.equals(p1)) return new ZipPath(zfs, Collections.emptyList(), false);
if (p1.isAbsolute() != p2.isAbsolute()) throw new IllegalArgumentException();
int l = Math.min(p1.path.size(), p2.path.size());
int common = 0;
while (common < l && Objects.equals(p1.path.get(common), p2.path.get(common))) common++;
int up = p1.path.size() - common;
List<String> result = new ArrayList<>();
for (int i = 0; i < up; i++) result.add("..");
result.addAll(p2.path);
return new ZipPath(zfs, result, false);
}
@NotNull
@Override
public URI toUri() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public ZipPath toAbsolutePath() {
if (isAbsolute()) {
return this;
}
return new ZipPath(zfs, path, true);
}
@NotNull
@Override
public ZipPath toRealPath(@NotNull LinkOption... options) throws IOException {
ZipPath absolute = toAbsolutePath().normalize();
absolute.checkAccess();
return absolute;
}
void checkAccess(AccessMode... modes) throws IOException {
boolean w = false;
boolean x = false;
for (AccessMode mode : modes) {
switch (mode) {
case READ:
break;
case WRITE:
w = true;
break;
case EXECUTE:
x = true;
break;
default:
throw new UnsupportedOperationException();
}
}
zfs.checkAccess(toAbsolutePath().normalize());
if ((w && zfs.isReadOnly()) || x) {
throw new AccessDeniedException(toString());
}
}
@NotNull
@Override
public File toFile() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public WatchKey register(@NotNull WatchService watcher, @NotNull WatchEvent.Kind<?> @NotNull [] events, WatchEvent.Modifier... modifiers) throws IOException {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public WatchKey register(@NotNull WatchService watcher, @NotNull WatchEvent.Kind<?> @NotNull ... events) throws IOException {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Iterator<Path> iterator() {
return new Iterator<Path>() {
private int index = 0;
public boolean hasNext() {
return index < getNameCount();
}
public Path next() {
if (index < getNameCount()) {
return getName(index++);
}
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ZipPath paths = (ZipPath) o;
return absolute == paths.absolute && path.equals(paths.path);
}
@Override
public int hashCode() {
return Objects.hash(path, absolute);
}
@Override
public int compareTo(@NotNull Path other) {
ZipPath p1 = this;
ZipPath p2 = ensurePath(other);
return p1.toString().compareTo(p2.toString());
}
ZipFileAttributes getAttributes() throws IOException {
ZipFileAttributes attributes = zfs.readAttributes(this);
if (attributes == null) throw new NoSuchFileException(toString());
else return attributes;
}
static List<String> getPathComponents(String path) {
List<String> components = new ArrayList<>();
int lastSlash = 0;
for (int i = 0; i <= path.length(); i++) {
if (i == path.length() || path.charAt(i) == '/' || path.charAt(i) == '\\') {
if (i != lastSlash) {
String component = path.substring(lastSlash, i);
components.add(component);
}
lastSlash = i + 1;
}
}
return components;
}
private static String normalizePath(String path) {
return String.join("/", getPathComponents(path));
}
static ZipPath ensurePath(Path path) {
if (path == null) throw new NullPointerException();
if (!(path instanceof ZipPath)) throw new ProviderMismatchException();
return (ZipPath) path;
}
String getEntryName() {
if (!isAbsolute()) throw new IllegalStateException();
return String.join("/", path);
}
@Override
public String toString() {
String str = String.join("/", path);
if (absolute) return "/" + str;
else return str;
}
}

View File

@ -0,0 +1,21 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.io;
public class ZipFileSystemTest {
}

View File

@ -0,0 +1,77 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.io;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Path;
import java.util.function.BiConsumer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class ZipPathTest {
ZipFileSystemProvider provider = new ZipFileSystemProvider();
ZipFileSystem zfs = new ZipFileSystem(provider, new ZipFile(new SeekableInMemoryByteChannel(IOUtils.readFullyAsByteArray(ZipPathTest.class.getResourceAsStream("/test.zip")))), true);
public ZipPathTest() throws IOException {
}
private Path p(String path) {
return zfs.getPath(path);
}
@Test
public void testNormalizePath() throws IOException {
BiConsumer<String, String> equals = (expected, actual) -> {
assertEquals(zfs.getPath(expected), zfs.getPath(actual).toAbsolutePath().normalize());
};
BiConsumer<String, String> notEquals = (expected, actual) -> {
assertNotEquals(zfs.getPath(expected), zfs.getPath(actual).toAbsolutePath().normalize());
};
equals.accept("/a/b/c/d", "/a\\b/c/d");
equals.accept("/a/b/c/d", "/a\\b/c/d/");
equals.accept("/a/b/c/d", "/a\\b/c//d");
equals.accept("/a/b/c/d", "/a\\\\b/c/d");
equals.accept("/a/b/c/d", "a/b/c/d");
equals.accept("/a/b/c/d", "a/b/.c/../c/d");
notEquals.accept("/a/b/c/d", "/a\\b/c");
}
@Test
public void testRelativizePath() throws IOException {
assertEquals(p("../../a/b/c"), p("/a/b/c/a/b/c").relativize(p("/a/b/c/d/e")));
assertEquals(p("../.."), p("/a/b/c").relativize(p("/a/b/c/d/e")));
assertEquals(p("../../"), p("/a/b/c").relativize(p("/a/b/c/d/e")));
assertEquals(p("../../"), p("/a/b/c/").relativize(p("/a/b/c/d/e")));
assertEquals(p("../.."), p("/a/b/c/").relativize(p("/a/b/c/d/e")));
assertEquals(p(""), p("/a/b/c/").relativize(p("/a/b/c")));
assertEquals(p(""), p("/a/b/c").relativize(p("/a/b/c")));
assertEquals(p(""), p("/a/b/c").relativize(p("/a/b/c/")));
assertEquals(p(""), p("/a/b/c/").relativize(p("/a/b/c/")));
}
}