Support Server Auto-Update Task. Closes #624

This commit is contained in:
huanghongxun
2019-11-11 17:34:19 +08:00
parent 177935241f
commit 4e0ffc8d1e
28 changed files with 614 additions and 50 deletions

View File

@@ -62,10 +62,10 @@ public final class MinecraftInstanceTask<T> extends Task<Void> {
Path root = fs.getPath(subDirectory);
if (Files.exists(root))
Files.walkFileTree(fs.getPath(subDirectory), new SimpleFileVisitor<Path>() {
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String relativePath = root.relativize(file).normalize().toString();
String relativePath = root.relativize(file).normalize().toString().replace(File.separatorChar, '/');
overrides.add(new ModpackConfiguration.FileInformation(relativePath, encodeHex(digest("SHA-1", file))));
return FileVisitResult.CONTINUE;
}

View File

@@ -36,7 +36,7 @@ public interface ModAdviser {
List<String> MODPACK_BLACK_LIST = Lang.immutableListOf(
"usernamecache.json", "usercache.json", // Minecraft
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
"pack.json", "launcher.jar", "hmclmc.log", "cache", // HMCL
"pack.json", "launcher.jar", "hmclmc.log", "cache", "modpack.cfg", // HMCL
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
"minetweaker.log", // Mods
".fabric", ".mixin.out", // Fabric

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.mod;
import java.nio.charset.Charset;
import java.util.List;
/**
*
@@ -85,4 +86,16 @@ public final class Modpack {
public Modpack setManifest(Object manifest) {
return new Modpack(name, author, version, gameVersion, description, encoding, manifest);
}
public static boolean acceptFile(String path, List<String> blackList, List<String> whiteList) {
if (path.isEmpty())
return true;
for (String s : blackList)
if (path.equals(s))
return false;
for (String s : whiteList)
if (path.equals(s))
return true;
return false;
}
}

View File

@@ -40,6 +40,15 @@ public class ModpackInstallTask<T> extends Task<Void> {
private final List<ModpackConfiguration.FileInformation> overrides;
private final Predicate<String> callback;
/**
* Constructor
* @param modpackFile a zip file
* @param dest destination to store unpacked files
* @param charset charset of the zip file
* @param subDirectory the subdirectory of zip file to unpack
* @param callback test whether the file (given full path) in zip file should be unpacked or not
* @param oldConfiguration old modpack information if upgrade
*/
public ModpackInstallTask(File modpackFile, File dest, Charset charset, String subDirectory, Predicate<String> callback, ModpackConfiguration<T> oldConfiguration) {
this.modpackFile = modpackFile;
this.dest = dest;

View File

@@ -52,7 +52,6 @@ public final class CurseCompletionTask extends Task<Void> {
private final ModManager modManager;
private final String version;
private CurseManifest manifest;
private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task<?>> dependencies = new LinkedList<>();
/**
@@ -94,11 +93,6 @@ public final class CurseCompletionTask extends Task<Void> {
return dependencies;
}
@Override
public Collection<Task<?>> getDependents() {
return dependents;
}
@Override
public void execute() throws Exception {
if (manifest == null)

View File

@@ -113,7 +113,7 @@ public final class CurseManifest {
}
/**
* @param f the CurseForge modpack file.
* @param zip the CurseForge modpack file.
* @throws IOException if the file is not a valid zip file.
* @throws JsonParseException if the manifest.json is missing or malformed.
* @return the manifest.

View File

@@ -21,6 +21,7 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.mod.ModAdviser;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -67,17 +68,7 @@ public class MultiMCModpackExportTask extends Task<Void> {
blackList.add(versionId + ".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 (Zipper zip = new Zipper(output.toPath())) {
zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> {
if (path.isEmpty())
return true;
for (String s : blackList)
if (path.equals(s))
return false;
for (String s : whitelist)
if (path.equals(s))
return true;
return false;
});
zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> Modpack.acceptFile(path, blackList, whitelist));
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId));
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId))

View File

@@ -0,0 +1,152 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.mod.server;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex;
public class ServerModpackCompletionTask extends Task<Void> {
private final DefaultGameRepository repository;
private final String version;
private ModpackConfiguration<ServerModpackManifest> manifest;
private GetTask dependent;
private ServerModpackManifest remoteManifest;
private final List<Task<?>> dependencies = new LinkedList<>();
public ServerModpackCompletionTask(DefaultDependencyManager dependencyManager, String version) {
this.repository = dependencyManager.getGameRepository();
this.version = version;
try {
File manifestFile = repository.getModpackConfiguration(version);
if (manifestFile.exists())
this.manifest = JsonUtils.GSON.fromJson(FileUtils.readText(manifestFile), new TypeToken<ModpackConfiguration<ServerModpackManifest>>() {
}.getType());
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Unable to read CurseForge modpack manifest.json", e);
}
}
@Override
public boolean doPreExecute() {
return true;
}
@Override
public void preExecute() throws Exception {
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
dependent = new GetTask(new URL(manifest.getManifest().getFileApi() + "/server-manifest.json"));
}
@Override
public Collection<Task<?>> getDependencies() {
return dependencies;
}
@Override
public Collection<Task<?>> getDependents() {
return dependent == null ? Collections.emptySet() : Collections.singleton(dependent);
}
@Override
public void execute() throws Exception {
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
try {
remoteManifest = JsonUtils.fromNonNullJson(dependent.getResult(), ServerModpackManifest.class);
} catch (JsonParseException e) {
throw new IOException(e);
}
Path rootPath = repository.getVersionRoot(version).toPath();
Map<String, ModpackConfiguration.FileInformation> files = manifest.getManifest().getFiles().stream()
.collect(Collectors.toMap(ModpackConfiguration.FileInformation::getPath,
Function.identity()));
Set<String> remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath)
.collect(Collectors.toSet());
// for files in new modpack
for (ModpackConfiguration.FileInformation file : remoteManifest.getFiles()) {
Path actualPath = rootPath.resolve(file.getPath());
boolean download;
if (!files.containsKey(file.getPath())) {
// If old modpack does not have this entry, download it
download = true;
} else if (!Files.exists(actualPath)) {
// If both old and new modpacks have this entry, but the file is deleted by user, leave it missing.
download = 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(actualPath)));
String oldHash = files.get(file.getPath()).getHash();
download = !Objects.equals(oldHash, file.getHash()) && Objects.equals(oldHash, fileHash);
}
if (download) {
dependencies.add(new FileDownloadTask(
new URL(remoteManifest.getFileApi() + "/overrides/" + file.getPath()),
actualPath.toFile(),
new FileDownloadTask.IntegrityCheck("SHA-1", file.getHash())));
}
}
// If old modpack have this entry, and new modpack deleted it. Delete this file.
for (ModpackConfiguration.FileInformation file : manifest.getManifest().getFiles()) {
Path actualPath = rootPath.resolve(file.getPath());
if (Files.exists(actualPath) && !remoteFiles.contains(file.getPath()))
Files.deleteIfExists(actualPath);
}
}
@Override
public boolean doPostExecute() {
return true;
}
@Override
public void postExecute() throws Exception {
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
File manifestFile = repository.getModpackConfiguration(version);
FileUtils.writeText(manifestFile, JsonUtils.GSON.toJson(new ModpackConfiguration<>(remoteManifest, this.manifest.getType(), remoteManifest.getFiles())));
}
}

View File

@@ -0,0 +1,106 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.mod.server;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.mod.ModAdviser;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.Zipper;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex;
public class ServerModpackExportTask extends Task<Void> {
private final DefaultGameRepository repository;
private final String versionId;
private final List<String> whitelist;
private final File output;
private final String modpackName;
private final String modpackAuthor;
private final String modpackVersion;
private final String modpackDescription;
public ServerModpackExportTask(DefaultGameRepository repository, String versionId, List<String> whitelist, String modpackName, String modpackAuthor, String modpackVersion, String modpackDescription, File output) {
this.repository = repository;
this.versionId = versionId;
this.whitelist = whitelist;
this.output = output;
this.modpackName = modpackName;
this.modpackAuthor = modpackAuthor;
this.modpackVersion = modpackVersion;
this.modpackDescription = modpackDescription;
onDone().register(event -> {
if (event.isFailed()) output.delete();
});
}
@Override
public void execute() throws Exception {
ArrayList<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);
blackList.add(versionId + ".jar");
blackList.add(versionId + ".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 (Zipper zip = new Zipper(output.toPath())) {
Path runDirectory = repository.getRunDirectory(versionId).toPath();
List<ModpackConfiguration.FileInformation> files = new ArrayList<>();
zip.putDirectory(runDirectory, ".minecraft", path -> {
if (Modpack.acceptFile(path, blackList, whitelist)) {
Path file = runDirectory.resolve(path);
if (Files.isRegularFile(file)) {
String relativePath = runDirectory.relativize(file).normalize().toString().replace(File.separatorChar, '/');
files.add(new ModpackConfiguration.FileInformation(relativePath, encodeHex(digest("SHA-1", file))));
}
return true;
} else {
return false;
}
});
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId));
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId))
.orElseThrow(() -> new IOException("Cannot parse the version of " + versionId));
List<ServerModpackManifest.Addon> addons = new ArrayList<>();
addons.add(new ServerModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion));
analyzer.getVersion(FORGE).ifPresent(forgeVersion ->
addons.add(new ServerModpackManifest.Addon(FORGE.getPatchId(), forgeVersion)));
analyzer.getVersion(LITELOADER).ifPresent(liteLoaderVersion ->
addons.add(new ServerModpackManifest.Addon(LITELOADER.getPatchId(), liteLoaderVersion)));
analyzer.getVersion(OPTIFINE).ifPresent(optifineVersion ->
addons.add(new ServerModpackManifest.Addon(OPTIFINE.getPatchId(), optifineVersion)));
analyzer.getVersion(FABRIC).ifPresent(fabricVersion ->
addons.add(new ServerModpackManifest.Addon(FABRIC.getPatchId(), fabricVersion)));
ServerModpackManifest manifest = new ServerModpackManifest(modpackName, modpackAuthor, modpackVersion, modpackDescription, "", files, addons);
zip.putTextFile(JsonUtils.GSON.toJson(manifest), "server-manifest.json");
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.mod.server;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.GameBuilder;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.ModpackInstallTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class ServerModpackInstallTask extends Task<Void> {
private final File zipFile;
private final Modpack modpack;
private final ServerModpackManifest manifest;
private final String name;
private final DefaultGameRepository repository;
private final List<Task<?>> dependencies = new LinkedList<>();
private final List<Task<?>> dependents = new LinkedList<>();
public ServerModpackInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, ServerModpackManifest manifest, String name) {
this.zipFile = zipFile;
this.modpack = modpack;
this.manifest = manifest;
this.name = name;
this.repository = dependencyManager.getGameRepository();
File run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name);
for (ServerModpackManifest.Addon addon : manifest.getAddons()) {
builder.version(addon.getId(), addon.getVersion());
}
dependents.add(builder.buildAsync());
onDone().register(event -> {
if (event.isFailed())
repository.removeVersionFromDisk(name);
});
ModpackConfiguration<ServerModpackManifest> config = null;
try {
if (json.exists()) {
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<ServerModpackManifest>>() {
}.getType());
if (!MODPACK_TYPE.equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version.");
}
} catch (JsonParseException | IOException ignore) {
}
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/overrides", any -> true, config));
}
@Override
public List<Task<?>> getDependents() {
return dependents;
}
@Override
public List<Task<?>> getDependencies() {
return dependencies;
}
@Override
public void execute() throws Exception {
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/overrides", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
}
public static final String MODPACK_TYPE = "Server";
}

View File

@@ -0,0 +1,129 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.mod.server;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
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 java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
public class ServerModpackManifest implements Validation {
private final String name;
private final String author;
private final String version;
private final String description;
private final String fileApi;
private final List<ModpackConfiguration.FileInformation> files;
private final List<Addon> addons;
public ServerModpackManifest() {
this("", "", "", "", "", Collections.emptyList(), Collections.emptyList());
}
public ServerModpackManifest(String name, String author, String version, String description, String fileApi, List<ModpackConfiguration.FileInformation> files, List<Addon> addons) {
this.name = name;
this.author = author;
this.version = version;
this.description = description;
this.fileApi = fileApi;
this.files = files;
this.addons = addons;
}
public String getName() {
return name;
}
public String getAuthor() {
return author;
}
public String getVersion() {
return version;
}
public String getDescription() {
return description;
}
public String getFileApi() {
return fileApi;
}
public List<ModpackConfiguration.FileInformation> getFiles() {
return files;
}
public List<Addon> getAddons() {
return addons;
}
@Override
public void validate() throws JsonParseException, TolerableValidationException {
if (fileApi == null)
throw new JsonParseException("ServerModpackManifest.fileApi cannot be blank");
if (files == null)
throw new JsonParseException("ServerModpackManifest.files cannot be null");
}
public static final class Addon {
private final String id;
private final String version;
public Addon() {
this("", "");
}
public Addon(String id, String version) {
this.id = id;
this.version = version;
}
public String getId() {
return id;
}
public String getVersion() {
return version;
}
}
/**
* @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.
* @return the manifest.
*/
public static Modpack readManifest(Path zip, Charset encoding) throws IOException, JsonParseException {
String json = CompressingUtils.readTextZipEntry(zip, "server-manifest.json", encoding);
ServerModpackManifest manifest = JsonUtils.fromNonNullJson(json, ServerModpackManifest.class);
String gameVersion = manifest.getAddons().stream().filter(x -> MINECRAFT.getPatchId().equals(x.getId())).findAny().orElseThrow(() -> new IOException("Cannot find game version")).getVersion();
return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), gameVersion, manifest.getDescription(), encoding, manifest);
}
}

View File

@@ -0,0 +1,22 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.function;
public interface ExceptionalPredicate<T, E extends Exception> {
boolean test(T t) throws E;
}

View File

@@ -18,6 +18,8 @@
package org.jackhuang.hmcl.util.io;
import org.jackhuang.hmcl.util.function.ExceptionalPredicate;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
@@ -25,7 +27,6 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.function.Predicate;
/**
* Non thread-safe
@@ -67,7 +68,7 @@ public final class Zipper implements Closeable {
* @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 {
public void putDirectory(Path source, String targetDir, ExceptionalPredicate<String, IOException> filter) throws IOException {
Path root = fs.getPath(targetDir);
Files.createDirectories(root);
Files.walkFileTree(source, new SimpleFileVisitor<Path>() {