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,17 +62,7 @@ public class HMCLModpackExportTask extends Task<Void> {
blackList.add(version + ".json");
Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
try (Zipper zip = new Zipper(output.toPath())) {
zip.putDirectory(repository.getRunDirectory(version).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(version).toPath(), "minecraft", path -> Modpack.acceptFile(path, blackList, whitelist));
Version mv = repository.getResolvedPreservingPatchesVersion(version);
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))

View File

@ -31,9 +31,10 @@ import org.jackhuang.hmcl.launch.NotDecompressingNativesException;
import org.jackhuang.hmcl.launch.PermissionException;
import org.jackhuang.hmcl.launch.ProcessCreationException;
import org.jackhuang.hmcl.launch.ProcessListener;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
@ -62,13 +63,7 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
@ -147,6 +142,8 @@ public final class LauncherHelper {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
if ("Curse".equals(configuration.getType()))
return new CurseCompletionTask(dependencyManager, selectedVersion);
else if ("Server".equals(configuration.getType()))
return new ServerModpackCompletionTask(dependencyManager, selectedVersion);
else
return null;
} catch (IOException e) {

View File

@ -25,6 +25,8 @@ import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
import org.jackhuang.hmcl.mod.curse.CurseManifest;
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
import org.jackhuang.hmcl.mod.server.ServerModpackInstallTask;
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
@ -65,6 +67,12 @@ public final class ModpackHelper {
// ignore it, not a valid MultiMC modpack.
}
try {
return ServerModpackManifest.readManifest(file, charset);
} catch (Exception e) {
// ignore it, not a valid Server modpack.
}
throw new UnsupportedModpackException(file.toString());
}
@ -120,7 +128,10 @@ public final class ModpackHelper {
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
.whenComplete(Schedulers.defaultScheduler(), success, failure)
.thenComposeAsync(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
else throw new IllegalArgumentException("Unrecognized modpack: " + modpack);
else if (modpack.getManifest() instanceof ServerModpackManifest)
return new ServerModpackInstallTask(profile.getDependency(), zipFile, modpack, ((ServerModpackManifest) modpack.getManifest()), name)
.whenComplete(Schedulers.defaultScheduler(), success, failure);
else throw new IllegalArgumentException("Unrecognized modpack: " + modpack.getManifest());
}
public static Task<Void> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException {

View File

@ -151,7 +151,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
if (manifest != null) {
WebStage stage = new WebStage();
stage.getWebView().getEngine().loadContent(manifest.getDescription());
stage.setTitle(i18n("modpack.wizard.step.3"));
stage.setTitle(i18n("modpack.description"));
stage.showAndWait();
}
}

View File

@ -55,7 +55,7 @@ public final class ModpackSelectionPage extends StackPane implements WizardPage
Optional<File> filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class);
if (filePath.isPresent()) {
controller.getSettings().put(MODPACK_FILE, filePath.get());
controller.onNext();
Platform.runLater(controller::onNext);
}
FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {

View File

@ -24,6 +24,7 @@ import org.jackhuang.hmcl.mod.ModAdviser;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;
import org.jackhuang.hmcl.mod.server.ServerModpackExportTask;
import org.jackhuang.hmcl.setting.Config;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.Profile;
@ -74,6 +75,8 @@ public final class ExportWizardProvider implements WizardProvider {
return exportAsHMCL(whitelist, modpackFile, modpackName, modpackAuthor, modpackVersion, modpackDescription, includeLauncher);
case ModpackTypeSelectionPage.MODPACK_TYPE_MULTIMC:
return exportAsMultiMC(whitelist, modpackFile, modpackName, modpackAuthor, modpackVersion, modpackDescription);
case ModpackTypeSelectionPage.MODPACK_TYPE_SERVER:
return exportAsServer(whitelist, modpackFile, modpackName, modpackAuthor, modpackVersion, modpackDescription);
default:
throw new IllegalStateException("Unrecognized modpack type " + modpackType);
}
@ -177,7 +180,22 @@ public final class ExportWizardProvider implements WizardProvider {
};
}
@Override
private Task<?> exportAsServer(List<String> whitelist, File modpackFile, String modpackName, String modpackAuthor, String modpackVersion, String modpackDescription) {
return new Task<Void>() {
Task<?> dependency;
@Override
public void execute() {
dependency = new ServerModpackExportTask(profile.getRepository(), version, whitelist, modpackName, modpackAuthor, modpackVersion, modpackDescription, modpackFile);
}
@Override
public Collection<Task<?>> getDependencies() {
return Collections.singleton(dependency);
}
};
}
@Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
switch (step) {
case 0: return new ModpackInfoPage(controller, version);

View File

@ -156,6 +156,7 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
public static final String MODPACK_FILE_SELECTION = "modpack.accepted";
private static final Map<String, String> TRANSLATION = mapOf(
pair("minecraft/hmclversion.cfg", i18n("modpack.files.hmclversion_cfg")),
pair("minecraft/servers.dat", i18n("modpack.files.servers_dat")),
pair("minecraft/saves", i18n("modpack.files.saves")),
pair("minecraft/mods", i18n("modpack.files.mods")),

View File

@ -34,13 +34,15 @@ public final class ModpackTypeSelectionPage extends StackPane implements WizardP
private JFXButton btnHMCL;
@FXML
private JFXButton btnMultiMC;
@FXML
private JFXButton btnServer;
public ModpackTypeSelectionPage(WizardController controller) {
this.controller = controller;
FXUtils.loadFXML(this, "/assets/fxml/modpack/type.fxml");
JFXButton[] buttons = new JFXButton[]{btnHMCL, btnMultiMC};
String[] types = new String[]{MODPACK_TYPE_HMCL, MODPACK_TYPE_MULTIMC};
JFXButton[] buttons = new JFXButton[]{btnHMCL, btnMultiMC, btnServer};
String[] types = new String[]{MODPACK_TYPE_HMCL, MODPACK_TYPE_MULTIMC, MODPACK_TYPE_SERVER};
for (int i = 0; i < types.length; ++i) {
String type = types[i];
buttons[i].setOnMouseClicked(e -> {
@ -61,6 +63,7 @@ public final class ModpackTypeSelectionPage extends StackPane implements WizardP
public static final String MODPACK_TYPE = "modpack.type";
public static final String MODPACK_TYPE_MULTIMC = "multimc";
public static final String MODPACK_TYPE_HMCL = "hmcl";
public static final String MODPACK_TYPE_MULTIMC = "multimc";
public static final String MODPACK_TYPE_SERVER = "server";
}

View File

@ -24,7 +24,7 @@
<BorderPane><left><Label text="%archive.version"/></left><right><Label fx:id="lblVersion" /></right></BorderPane>
<BorderPane><left><Label text="%archive.author"/></left><right><Label fx:id="lblAuthor" /></right></BorderPane>
<BorderPane>
<left><JFXButton fx:id="btnDescription" onMouseClicked="#onDescribe" text="%modpack.wizard.step.3" styleClass="jfx-button-border" /></left>
<left><JFXButton fx:id="btnDescription" onMouseClicked="#onDescribe" text="%modpack.description" styleClass="jfx-button-border" /></left>
<right><JFXButton buttonType="RAISED" fx:id="btnInstall" onMouseClicked="#onInstall" text="%button.install" styleClass="jfx-button-raised" /></right>
</BorderPane>
</ComponentList>

View File

@ -37,5 +37,17 @@
</BorderPane>
</graphic>
</JFXButton>
<JFXButton fx:id="btnServer" prefWidth="${list.width}">
<graphic>
<BorderPane mouseTransparent="true">
<left>
<TwoLineListItem title="%modpack.type.server" subtitle="%modpack.type.server.export" />
</left>
<right>
<fx:include BorderPane.alignment="CENTER" source="/assets/svg/arrow-right.fxml"/>
</right>
</BorderPane>
</graphic>
</JFXButton>
</VBox>
</fx:root>

View File

@ -224,12 +224,14 @@ modpack.choose.remote=Download modpack from Internet
modpack.choose.remote.detail=Requires a direct download link to the remote modpack file
modpack.choose.remote.tooltip=A direct download link to the remote modpack file
modpack.desc=Describe your modpack, including precautions and changelog. Markdown and online pictures are supported.
modpack.description=Description
modpack.enter_name=Enter a name for this modpack.
modpack.export=Export Modpack
modpack.export.as=Export Modpack As...
modpack.files.blueprints=BuildCraft blueprints
modpack.files.config=Mod configs
modpack.files.dumps=NEI debug output
modpack.files.hmclversion_cfg=Launcher configuration file
modpack.files.liteconfig=Mod configurations
modpack.files.mods=Mods
modpack.files.mods.voxelmods=VoxelMods (including VoxelMap) options
@ -259,6 +261,8 @@ modpack.type.hmcl=Hello Minecraft! Launcher
modpack.type.hmcl.export=Can be imported by Hello Minecraft! Launcher
modpack.type.multimc=MultiMC
modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC
modpack.type.server=Server Auto-Update Modpack
modpack.type.server.export=Allow server manager updating game client remotely
modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, and Curse modpacks are supported.
modpack.update=Game Update
modpack.wizard=Exporting modpack wizard

View File

@ -218,6 +218,7 @@ message.warning=Advertencia
modpack=Modpack
modpack.choose=Escoja el modpack en archivo zip que quieras instalar.
modpack.desc=Describa su modpack, incluyendo precauciones, changelog, soportando Markdown (también soportando imágenes en línea).
modpack.description=Descripción
modpack.enter_name=Entre su nombre deseado para este juego.
modpack.export=Exportar modpack
modpack.export.as=Exportar modpack como...

View File

@ -218,6 +218,7 @@ message.warning=Предупреждение
modpack=Модпак
modpack.choose=Выберите zip-файл модпака, который вы хотите установить.
modpack.desc=Опишите свой модпак, включая меры предосторожности, журнал изменений, поддерживая Markdown (также поддерживая онлайн-изображения).
modpack.description=описание
modpack.enter_name=Введите желаемое имя для этой версии.
modpack.export=Экспортировать модпак
modpack.export.as=Экспорт модпака как...

View File

@ -221,12 +221,14 @@ modpack.choose.remote=從網路下載整合包
modpack.choose.remote.detail=需要提供整合包的下載連結
modpack.choose.remote.tooltip=要下載的整合包的連結
modpack.desc=描述你要製作的整合包,比如整合包注意事項和更新記錄,支援 Markdown圖片請上傳至網路
modpack.description=整合包描述
modpack.enter_name=給遊戲取個你喜歡的名字
modpack.export=匯出整合包
modpack.export.as=請選擇整合包類型。若你無法決定,請選擇 HMCL 類型。
modpack.files.blueprints=BuildCraft 藍圖
modpack.files.config=Mod 模組設定檔案
modpack.files.dumps=NEI 調校輸出
modpack.files.hmclversion_cfg=啟動器設定檔案
modpack.files.liteconfig=Mod 模組設定檔案
modpack.files.mods=Mod 模組
modpack.files.mods.voxelmods=VoxelMods 設定,如小地圖
@ -256,6 +258,8 @@ modpack.type.hmcl=HMCL
modpack.type.hmcl.export=可以被 Hello Minecraft! Launcher (HMCL) 導入
modpack.type.multimc=MultiMC
modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 導入
modpack.type.server=伺服器自動更新整合包
modpack.type.server.export=允許伺服器管理員遠程更新遊戲用戶端
modpack.unsupported=不支援該整合包。僅 HMCL、MultiMC、Curse 整合包受支援。
modpack.update=正在升級整合包
modpack.wizard=匯出整合包引導

View File

@ -222,12 +222,14 @@ modpack.choose.remote=从互联网下载整合包
modpack.choose.remote.detail=需要提供整合包的下载链接
modpack.choose.remote.tooltip=要下载的整合包的链接
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown图片请用网络图
modpack.description=整合包描述
modpack.enter_name=给游戏起个你喜欢的名字
modpack.export=导出整合包
modpack.export.as=请选择整合包类型。若你无法决定,请选择 HMCL 类型。
modpack.files.blueprints=BuildCraft 蓝图
modpack.files.config=Mod 配置文件
modpack.files.dumps=NEI 调试输出
modpack.files.hmclversion_cfg=启动器配置文件
modpack.files.liteconfig=Mod 配置文件
modpack.files.mods=Mod
modpack.files.mods.voxelmods=VoxelMods 配置,如小地图
@ -257,6 +259,8 @@ modpack.type.hmcl=HMCL
modpack.type.hmcl.export=可以被 Hello Minecraft! Launcher (HMCL) 导入
modpack.type.multimc=MultiMC
modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 导入
modpack.type.server=服务器自动更新整合包
modpack.type.server.export=允许服务器管理员远程更新游戏客户端
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
modpack.update=正在升级整合包
modpack.wizard=导出整合包向导

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