将 Unzipper 迁移至 kala-compress (#5148)
This commit is contained in:
@@ -397,9 +397,12 @@ public class DefaultLauncher extends Launcher {
|
||||
for (Library library : version.getLibraries())
|
||||
if (library.isNative())
|
||||
new Unzipper(repository.getLibraryFile(version, library), destination)
|
||||
.setFilter((zipEntry, isDirectory, destFile, path) -> {
|
||||
if (!isDirectory && Files.isRegularFile(destFile) && Files.size(destFile) == Files.size(zipEntry))
|
||||
.setFilter((zipEntry, destFile, relativePath) -> {
|
||||
if (!zipEntry.isDirectory() && !zipEntry.isUnixSymlink()
|
||||
&& Files.isRegularFile(destFile)
|
||||
&& zipEntry.getSize() == Files.size(destFile)) {
|
||||
return false;
|
||||
}
|
||||
String ext = FileUtils.getExtension(destFile);
|
||||
if (ext.equals("sha1") || ext.equals("git"))
|
||||
return false;
|
||||
@@ -411,7 +414,7 @@ public class DefaultLauncher extends Launcher {
|
||||
return false;
|
||||
}
|
||||
|
||||
return library.getExtract().shouldExtract(path);
|
||||
return library.getExtract().shouldExtract(relativePath);
|
||||
})
|
||||
.setReplaceExistentFile(false).unzip();
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -73,22 +73,22 @@ public class ModpackInstallTask<T> extends Task<Void> {
|
||||
.setTerminateIfSubDirectoryNotExists()
|
||||
.setReplaceExistentFile(true)
|
||||
.setEncoding(charset)
|
||||
.setFilter((destPath, isDirectory, zipEntry, entryPath) -> {
|
||||
if (isDirectory) return true;
|
||||
if (!callback.test(entryPath)) return false;
|
||||
entries.add(entryPath);
|
||||
.setFilter((zipEntry, destFile, relativePath) -> {
|
||||
if (zipEntry.isDirectory()) return true;
|
||||
if (!callback.test(relativePath)) return false;
|
||||
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.
|
||||
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.
|
||||
return false;
|
||||
} else {
|
||||
// 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.
|
||||
String fileHash = DigestUtils.digestToString("SHA-1", destPath);
|
||||
String oldHash = files.get(entryPath).getHash();
|
||||
String fileHash = DigestUtils.digestToString("SHA-1", destFile);
|
||||
String oldHash = files.get(relativePath).getHash();
|
||||
return Objects.equals(oldHash, fileHash);
|
||||
}
|
||||
}).unzip();
|
||||
|
||||
@@ -17,57 +17,54 @@
|
||||
*/
|
||||
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.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
public final class Unzipper {
|
||||
private final Path zipFile, dest;
|
||||
private boolean replaceExistentFile = false;
|
||||
private boolean terminateIfSubDirectoryNotExists = false;
|
||||
private String subDirectory = "/";
|
||||
private FileFilter filter = null;
|
||||
private EntryFilter filter;
|
||||
private Charset encoding = StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if replace the existent files in destination directory,
|
||||
* otherwise those conflict files will be ignored.
|
||||
*/
|
||||
/// 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) {
|
||||
/// Will be called for every entry in the zip file.
|
||||
/// Callback returns false if you want leave the specific file uncompressed.
|
||||
public Unzipper setFilter(EntryFilter 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: "/"
|
||||
*/
|
||||
/// 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;
|
||||
@@ -83,53 +80,107 @@ public final class Unzipper {
|
||||
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.readonly(zipFile).setEncoding(encoding).setAutoDetectEncoding(true).build()) {
|
||||
Path root = fs.getPath(subDirectory);
|
||||
if (!root.isAbsolute() || (subDirectory.length() > 1 && subDirectory.endsWith("/")))
|
||||
throw new IllegalArgumentException("Subdirectory for unzipper must be absolute");
|
||||
private ZipArchiveReader openReader() throws IOException {
|
||||
ZipArchiveReader zipReader = new ZipArchiveReader(Files.newByteChannel(zipFile));
|
||||
|
||||
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;
|
||||
Charset suitableEncoding;
|
||||
try {
|
||||
Files.copy(file, destFile, replaceExistentFile ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{});
|
||||
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 {
|
||||
Path destDir = this.dest.toAbsolutePath().normalize();
|
||||
Files.createDirectories(destDir);
|
||||
|
||||
CopyOption[] copyOptions = replaceExistentFile
|
||||
? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING}
|
||||
: new CopyOption[]{};
|
||||
|
||||
long entryCount = 0L;
|
||||
try (ZipArchiveReader reader = openReader()) {
|
||||
String pathPrefix = StringUtils.addSuffix(subDirectory, "/");
|
||||
|
||||
for (ZipArchiveEntry entry : reader.getEntries()) {
|
||||
String normalizedPath = FileUtils.normalizePath(entry.getName());
|
||||
if (!normalizedPath.startsWith(pathPrefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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)
|
||||
Files.deleteIfExists(destFile);
|
||||
|
||||
Path targetPath;
|
||||
try {
|
||||
targetPath = Path.of(linkTarget);
|
||||
} catch (InvalidPathException e) {
|
||||
throw new IOException("Zip entry has an invalid symlink target: " + entry.getName(), e);
|
||||
}
|
||||
|
||||
if (!destFile.getParent().resolve(targetPath).toAbsolutePath().normalize().startsWith(destDir)) {
|
||||
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;
|
||||
}
|
||||
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.SKIP_SUBTREE;
|
||||
Files.createDirectories(dirToCreate);
|
||||
return FileVisitResult.CONTINUE;
|
||||
if (entry.getUnixMode() != 0 && OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) {
|
||||
Files.setPosixFilePermissions(destFile, FileUtils.parsePosixFilePermission(entry.getUnixMode()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public interface FileFilter {
|
||||
boolean accept(Path zipEntry, boolean isDirectory, Path destFile, String entryPath) throws IOException;
|
||||
if (entryCount == 0 && !terminateIfSubDirectoryNotExists) {
|
||||
throw new NoSuchFileException("Subdirectory " + subDirectory + " does not exist in the zip file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EntryFilter {
|
||||
boolean accept(ZipArchiveEntry zipArchiveEntry, Path destFile, String relativePath) throws IOException;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user