将 Unzipper 迁移至 kala-compress (#5148)
This commit is contained in:
@@ -397,9 +397,12 @@ public class DefaultLauncher extends Launcher {
|
|||||||
for (Library library : version.getLibraries())
|
for (Library library : version.getLibraries())
|
||||||
if (library.isNative())
|
if (library.isNative())
|
||||||
new Unzipper(repository.getLibraryFile(version, library), destination)
|
new Unzipper(repository.getLibraryFile(version, library), destination)
|
||||||
.setFilter((zipEntry, isDirectory, destFile, path) -> {
|
.setFilter((zipEntry, destFile, relativePath) -> {
|
||||||
if (!isDirectory && Files.isRegularFile(destFile) && Files.size(destFile) == Files.size(zipEntry))
|
if (!zipEntry.isDirectory() && !zipEntry.isUnixSymlink()
|
||||||
|
&& Files.isRegularFile(destFile)
|
||||||
|
&& zipEntry.getSize() == Files.size(destFile)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
String ext = FileUtils.getExtension(destFile);
|
String ext = FileUtils.getExtension(destFile);
|
||||||
if (ext.equals("sha1") || ext.equals("git"))
|
if (ext.equals("sha1") || ext.equals("git"))
|
||||||
return false;
|
return false;
|
||||||
@@ -411,7 +414,7 @@ public class DefaultLauncher extends Launcher {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return library.getExtract().shouldExtract(path);
|
return library.getExtract().shouldExtract(relativePath);
|
||||||
})
|
})
|
||||||
.setReplaceExistentFile(false).unzip();
|
.setReplaceExistentFile(false).unzip();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -73,22 +73,22 @@ public class ModpackInstallTask<T> extends Task<Void> {
|
|||||||
.setTerminateIfSubDirectoryNotExists()
|
.setTerminateIfSubDirectoryNotExists()
|
||||||
.setReplaceExistentFile(true)
|
.setReplaceExistentFile(true)
|
||||||
.setEncoding(charset)
|
.setEncoding(charset)
|
||||||
.setFilter((destPath, isDirectory, zipEntry, entryPath) -> {
|
.setFilter((zipEntry, destFile, relativePath) -> {
|
||||||
if (isDirectory) return true;
|
if (zipEntry.isDirectory()) return true;
|
||||||
if (!callback.test(entryPath)) return false;
|
if (!callback.test(relativePath)) return false;
|
||||||
entries.add(entryPath);
|
entries.add(relativePath);
|
||||||
|
|
||||||
if (!files.containsKey(entryPath)) {
|
if (!files.containsKey(relativePath)) {
|
||||||
// If old modpack does not have this entry, add this entry or override the file that user added.
|
// If old modpack does not have this entry, add this entry or override the file that user added.
|
||||||
return true;
|
return true;
|
||||||
} else if (!Files.exists(destPath)) {
|
} else if (!Files.exists(destFile)) {
|
||||||
// If both old and new modpacks have this entry, but the file is deleted by user, leave it missing.
|
// If both old and new modpacks have this entry, but the file is deleted by user, leave it missing.
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// If both old and new modpacks have this entry, and user has modified this file,
|
// If both old and new modpacks have this entry, and user has modified this file,
|
||||||
// we will not replace it since this modified file is what user expects.
|
// we will not replace it since this modified file is what user expects.
|
||||||
String fileHash = DigestUtils.digestToString("SHA-1", destPath);
|
String fileHash = DigestUtils.digestToString("SHA-1", destFile);
|
||||||
String oldHash = files.get(entryPath).getHash();
|
String oldHash = files.get(relativePath).getHash();
|
||||||
return Objects.equals(oldHash, fileHash);
|
return Objects.equals(oldHash, fileHash);
|
||||||
}
|
}
|
||||||
}).unzip();
|
}).unzip();
|
||||||
|
|||||||
@@ -17,57 +17,54 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util.io;
|
package org.jackhuang.hmcl.util.io;
|
||||||
|
|
||||||
|
import kala.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
import kala.compress.archivers.zip.ZipArchiveReader;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
|
|
||||||
public final class Unzipper {
|
public final class Unzipper {
|
||||||
private final Path zipFile, dest;
|
private final Path zipFile, dest;
|
||||||
private boolean replaceExistentFile = false;
|
private boolean replaceExistentFile = false;
|
||||||
private boolean terminateIfSubDirectoryNotExists = false;
|
private boolean terminateIfSubDirectoryNotExists = false;
|
||||||
private String subDirectory = "/";
|
private String subDirectory = "/";
|
||||||
private FileFilter filter = null;
|
private EntryFilter filter;
|
||||||
private Charset encoding = StandardCharsets.UTF_8;
|
private Charset encoding = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/// Decompress the given zip file to a directory.
|
||||||
* Decompress the given zip file to a directory.
|
///
|
||||||
*
|
/// @param zipFile the input zip file to be uncompressed
|
||||||
* @param zipFile the input zip file to be uncompressed
|
/// @param destDir the dest directory to hold uncompressed files
|
||||||
* @param destDir the dest directory to hold uncompressed files
|
|
||||||
*/
|
|
||||||
public Unzipper(Path zipFile, Path destDir) {
|
public Unzipper(Path zipFile, Path destDir) {
|
||||||
this.zipFile = zipFile;
|
this.zipFile = zipFile;
|
||||||
this.dest = destDir;
|
this.dest = destDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// True if replace the existent files in destination directory,
|
||||||
* True if replace the existent files in destination directory,
|
/// otherwise those conflict files will be ignored.
|
||||||
* otherwise those conflict files will be ignored.
|
|
||||||
*/
|
|
||||||
public Unzipper setReplaceExistentFile(boolean replaceExistentFile) {
|
public Unzipper setReplaceExistentFile(boolean replaceExistentFile) {
|
||||||
this.replaceExistentFile = replaceExistentFile;
|
this.replaceExistentFile = replaceExistentFile;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Will be called for every entry in the zip file.
|
||||||
* Will be called for every entry in the zip file.
|
/// Callback returns false if you want leave the specific file uncompressed.
|
||||||
* Callback returns false if you want leave the specific file uncompressed.
|
public Unzipper setFilter(EntryFilter filter) {
|
||||||
*/
|
|
||||||
public Unzipper setFilter(FileFilter filter) {
|
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Will only uncompress files in the "subDirectory", their path will be also affected.
|
||||||
* 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
|
||||||
* 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.
|
||||||
* uncompressed to the destination directory without creating META-INF folder.
|
///
|
||||||
*
|
/// Default value: "/"
|
||||||
* Default value: "/"
|
|
||||||
*/
|
|
||||||
public Unzipper setSubDirectory(String subDirectory) {
|
public Unzipper setSubDirectory(String subDirectory) {
|
||||||
this.subDirectory = FileUtils.normalizePath(subDirectory);
|
this.subDirectory = FileUtils.normalizePath(subDirectory);
|
||||||
return this;
|
return this;
|
||||||
@@ -83,53 +80,107 @@ public final class Unzipper {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private ZipArchiveReader openReader() throws IOException {
|
||||||
* Decompress the given zip file to a directory.
|
ZipArchiveReader zipReader = new ZipArchiveReader(Files.newByteChannel(zipFile));
|
||||||
*
|
|
||||||
* @throws IOException if zip file is malformed or filesystem error.
|
Charset suitableEncoding;
|
||||||
*/
|
try {
|
||||||
|
if (encoding != StandardCharsets.UTF_8 && CompressingUtils.testEncoding(zipReader, encoding)) {
|
||||||
|
suitableEncoding = encoding;
|
||||||
|
} else {
|
||||||
|
suitableEncoding = CompressingUtils.findSuitableEncoding(zipReader);
|
||||||
|
if (suitableEncoding == StandardCharsets.UTF_8)
|
||||||
|
return zipReader;
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
IOUtils.closeQuietly(zipReader, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
zipReader.close();
|
||||||
|
return new ZipArchiveReader(Files.newByteChannel(zipFile), suitableEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decompress the given zip file to a directory.
|
||||||
|
///
|
||||||
|
/// @throws IOException if zip file is malformed or filesystem error.
|
||||||
public void unzip() throws IOException {
|
public void unzip() throws IOException {
|
||||||
Files.createDirectories(dest);
|
Path destDir = this.dest.toAbsolutePath().normalize();
|
||||||
try (FileSystem fs = CompressingUtils.readonly(zipFile).setEncoding(encoding).setAutoDetectEncoding(true).build()) {
|
Files.createDirectories(destDir);
|
||||||
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))
|
CopyOption[] copyOptions = replaceExistentFile
|
||||||
return;
|
? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING}
|
||||||
|
: new CopyOption[]{};
|
||||||
|
|
||||||
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
|
long entryCount = 0L;
|
||||||
@Override
|
try (ZipArchiveReader reader = openReader()) {
|
||||||
public FileVisitResult visitFile(Path file,
|
String pathPrefix = StringUtils.addSuffix(subDirectory, "/");
|
||||||
BasicFileAttributes attrs) throws IOException {
|
|
||||||
String relativePath = root.relativize(file).toString();
|
for (ZipArchiveEntry entry : reader.getEntries()) {
|
||||||
Path destFile = dest.resolve(relativePath);
|
String normalizedPath = FileUtils.normalizePath(entry.getName());
|
||||||
if (filter != null && !filter.accept(file, false, destFile, relativePath))
|
if (!normalizedPath.startsWith(pathPrefix)) {
|
||||||
return FileVisitResult.CONTINUE;
|
continue;
|
||||||
try {
|
}
|
||||||
Files.copy(file, destFile, replaceExistentFile ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{});
|
|
||||||
} catch (FileAlreadyExistsException e) {
|
String relativePath = normalizedPath.substring(pathPrefix.length());
|
||||||
|
Path destFile = destDir.resolve(relativePath).toAbsolutePath().normalize();
|
||||||
|
if (!destFile.startsWith(destDir)) {
|
||||||
|
throw new IOException("Zip entry is trying to write outside of the destination directory: " + entry.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter != null && !filter.accept(entry, destFile, relativePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entryCount++;
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
Files.createDirectories(destFile);
|
||||||
|
} else {
|
||||||
|
Files.createDirectories(destFile.getParent());
|
||||||
|
if (entry.isUnixSymlink()) {
|
||||||
|
String linkTarget = reader.getUnixSymlink(entry);
|
||||||
if (replaceExistentFile)
|
if (replaceExistentFile)
|
||||||
throw e;
|
Files.deleteIfExists(destFile);
|
||||||
}
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
Path targetPath;
|
||||||
public FileVisitResult preVisitDirectory(Path dir,
|
try {
|
||||||
BasicFileAttributes attrs) throws IOException {
|
targetPath = Path.of(linkTarget);
|
||||||
String relativePath = root.relativize(dir).toString();
|
} catch (InvalidPathException e) {
|
||||||
Path dirToCreate = dest.resolve(relativePath);
|
throw new IOException("Zip entry has an invalid symlink target: " + entry.getName(), e);
|
||||||
if (filter != null && !filter.accept(dir, true, dirToCreate, relativePath))
|
}
|
||||||
return FileVisitResult.SKIP_SUBTREE;
|
|
||||||
Files.createDirectories(dirToCreate);
|
if (!destFile.getParent().resolve(targetPath).toAbsolutePath().normalize().startsWith(destDir)) {
|
||||||
return FileVisitResult.CONTINUE;
|
throw new IOException("Zip entry is trying to create a symlink outside of the destination directory: " + entry.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Files.createSymbolicLink(destFile, targetPath);
|
||||||
|
} catch (FileAlreadyExistsException ignored) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try (InputStream input = reader.getInputStream(entry)) {
|
||||||
|
Files.copy(input, destFile, copyOptions);
|
||||||
|
} catch (FileAlreadyExistsException e) {
|
||||||
|
if (replaceExistentFile)
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.getUnixMode() != 0 && OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) {
|
||||||
|
Files.setPosixFilePermissions(destFile, FileUtils.parsePosixFilePermission(entry.getUnixMode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (entryCount == 0 && !terminateIfSubDirectoryNotExists) {
|
||||||
|
throw new NoSuchFileException("Subdirectory " + subDirectory + " does not exist in the zip file.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface FileFilter {
|
@FunctionalInterface
|
||||||
boolean accept(Path zipEntry, boolean isDirectory, Path destFile, String entryPath) throws IOException;
|
public interface EntryFilter {
|
||||||
|
boolean accept(ZipArchiveEntry zipArchiveEntry, Path destFile, String relativePath) throws IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user