Compare commits

...
25 Commits
Author SHA1 Message Date
huangyuhui c6b49545ff Fix #424 2018-08-19 12:13:39 +08:00
huangyuhui a4a833bfdb Fix auto-installing: missing checksums in game json 2018-08-19 11:58:02 +08:00
huangyuhui 2e1b6e4630 Fix not correctly loading remote game version list 2018-08-19 11:56:39 +08:00
huangyuhui 6911cfa387 try to move deleting version to trash 2018-08-19 11:34:07 +08:00
huangyuhui aa29f890de Log memory size and metaspace 2018-08-19 02:03:05 +08:00
huangyuhui 0f4e6a01bc Fix unable to install more than one libraries 2018-08-19 01:18:57 +08:00
huangyuhui 1d3d8d6710 Show OptiFine versions in dividually 2018-08-19 00:39:47 +08:00
huangyuhui 005e06c56f Always download Optifine from BMCLAPI 2018-08-19 00:35:37 +08:00
huangyuhui 0a7c32b3cb Friendly prompt warning user about independent version deletion 2018-08-18 22:40:08 +08:00
huangyuhui 82916a04ea Check if forge libraries' checksums are valid 2018-08-18 22:25:13 +08:00
huangyuhui ba2e419894 Fix #422 2018-08-18 22:09:16 +08:00
huangyuhui 0812c9025d Do not check game asset hash code for every launching 2018-08-18 02:00:00 +08:00
huangyuhui 4c59ed3ccf Fix installing MultiMC modpack 2018-08-18 01:12:22 +08:00
huangyuhui 43d8d96608 Add doublequotes when doublequotes exist in windows batch 2018-08-18 00:43:55 +08:00
huangyuhui 26a6a7d883 Add doublequotes when special characters exist in launch script. Closes #420 2018-08-18 00:32:37 +08:00
huangyuhui 2e9c237367 Use system proxy by default 2018-08-17 23:48:01 +08:00
huangyuhui 99d2816ea7 Suppress RuntimeException since -1 is possible 2018-08-17 22:11:46 +08:00
huangyuhui 7c3853b0e6 Not logging command line in ProcessStoppedEvent 2018-08-17 22:10:11 +08:00
huangyuhui 53aecf38e0 Support displaying Rift mod information 2018-08-17 22:09:37 +08:00
huangyuhui 41fab0a781 Log launching game version name 2018-08-17 21:56:52 +08:00
huangyuhui 3df35aa373 Translate ZipError into ZipException since ZipError is not IOException 2018-08-17 21:53:29 +08:00
huangyuhui 543eaa4f66 Force cancel using custom log4j config 2018-08-17 21:52:35 +08:00
huangyuhui 3a851b7f1a Fix unable to launch 1.13 with Rift 2018-08-17 21:51:25 +08:00
huangyuhui 089ae1e313 Suppress InterruptedException 2018-08-17 21:51:12 +08:00
huangyuhui bfe5802d02 Remove roboto font 2018-08-16 02:46:49 +08:00
61 changed files with 575 additions and 514 deletions

No files matched your search

@@ -34,6 +34,7 @@ import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
@@ -101,6 +102,9 @@ public final class Launcher extends Application {
LOG.info("Java Home: " + System.getProperty("java.home"));
LOG.info("Current Directory: " + Paths.get("").toAbsolutePath());
LOG.info("HMCL Directory: " + HMCL_DIRECTORY);
LOG.info("Memory: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB");
ManagementFactory.getMemoryPoolMXBeans().stream().filter(bean -> bean.getName().equals("Metaspace")).findAny()
.ifPresent(bean -> LOG.info("Metaspace: " + bean.getUsage().getUsed() / 1024 / 1024 + "MB"));
launch(args);
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
@@ -40,6 +40,8 @@ import org.jackhuang.hmcl.upgrade.UpdateHandler;
public final class Main {
public static void main(String[] args) {
System.setProperty("java.net.useSystemProxies", "true");
checkJavaFX();
checkDirectoryPath();
checkDSTRootCAX3();
@@ -65,6 +65,8 @@ public final class LauncherHelper {
if (account == null)
throw new IllegalArgumentException("No account");
Logging.LOG.info("Launching game version: " + selectedVersion);
GameRepository repository = profile.getRepository();
Version version = repository.getResolvedVersion(selectedVersion);
@@ -388,10 +390,13 @@ public final class LauncherHelper {
logs.removeFirst();
if (setting.isShowLogs()) {
Lang.invoke(() -> {
try {
latch.await();
logWindow.waitForLoaded();
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
Platform.runLater(() -> logWindow.logLine(log, level));
}
@@ -88,8 +88,6 @@ public class Theme {
}
return new String[]{
Theme.class.getResource("/css/jfoenix-fonts.css").toExternalForm(),
Theme.class.getResource("/css/jfoenix-design.css").toExternalForm(),
css,
Theme.class.getResource("/assets/css/root.css").toExternalForm()
};
@@ -26,6 +26,8 @@ import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.mod.ModInfo;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.util.StringUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.function.Consumer;
@@ -54,7 +56,14 @@ public final class ModItem extends BorderPane {
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
modItem.setTitle(info.getFileName());
modItem.setSubtitle(info.getName() + ", " + i18n("archive.version") + ": " + info.getVersion() + ", " + i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + i18n("archive.author") + ": " + info.getAuthors());
StringBuilder message = new StringBuilder(info.getName());
if (StringUtils.isNotBlank(info.getVersion()))
message.append(", ").append(i18n("archive.version")).append(": ").append(info.getVersion());
if (StringUtils.isNotBlank(info.getGameVersion()))
message.append(", ").append(i18n("archive.game_version")).append(": ").append(info.getGameVersion());
if (StringUtils.isNotBlank(info.getAuthors()))
message.append(", ").append(i18n("archive.author")).append(": ").append(info.getAuthors());
modItem.setSubtitle(message.toString());
chkEnabled.setSelected(info.isActive());
chkEnabled.selectedProperty().addListener((a, b, newValue) ->
info.activeProperty().set(newValue));
@@ -103,7 +103,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
@FXML
private StackPane themeColorPickerContainer;
@FXML
private JFXCheckBox chkEnableProxy;
private JFXCheckBox chkDisableProxy;
@FXML
private JFXRadioButton chkProxyHttp;
@FXML
@@ -160,10 +160,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
txtProxyUsername.textProperty().bindBidirectional(config().proxyUserProperty());
txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());
proxyPane.disableProperty().bind(chkEnableProxy.selectedProperty().not());
proxyPane.disableProperty().bind(chkDisableProxy.selectedProperty());
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
chkEnableProxy.selectedProperty().bindBidirectional(config().hasProxyProperty());
chkDisableProxy.setSelected(!config().hasProxy());
chkDisableProxy.selectedProperty().addListener(o -> config().setHasProxy(!chkDisableProxy.isSelected()));
config().hasProxyProperty().addListener(o -> chkDisableProxy.setSelected(!config().hasProxy()));
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
selectedProxyType = new SimpleObjectProperty<Proxy.Type>(Proxy.Type.HTTP) {
@@ -26,16 +26,17 @@ import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Tab;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.FileUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class VersionPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", null);
@@ -147,7 +148,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
sub = "saves";
break;
default:
throw new RuntimeException();
return;
}
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
}
@@ -186,7 +187,12 @@ public final class VersionPage extends StackPane implements DecoratorPage {
}
public static void deleteVersion(Profile profile, String version) {
Controllers.confirmDialog(i18n("version.manage.remove.confirm", version), i18n("message.confirm"), () -> {
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == EnumGameDirectory.VERSION_FOLDER;
boolean isMovingToTrashSupported = FileUtils.isMovingToTrashSupported();
String message = isIndependent ? i18n("version.manage.remove.confirm.independent", version) :
isMovingToTrashSupported ? i18n("version.manage.remove.confirm.trash", version, version + "_removed") :
i18n("version.manage.remove.confirm", version);
Controllers.confirmDialog(message, i18n("message.confirm"), () -> {
if (profile.getRepository().removeVersionFromDisk(version)) {
profile.getRepository().refreshVersionsAsync().start();
Controllers.navigate(null);
@@ -103,7 +103,7 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
}
private String getVersion(String id) {
return Optional.ofNullable(controller.getSettings().get(id)).map(it -> (RemoteVersion<?>) it).map(RemoteVersion::getSelfVersion).orElse(null);
return Optional.ofNullable(controller.getSettings().get(id)).map(it -> (RemoteVersion) it).map(RemoteVersion::getSelfVersion).orElse(null);
}
@Override
@@ -49,16 +49,16 @@ public final class DownloadWizardProvider implements WizardProvider {
GameBuilder builder = profile.getDependency().gameBuilder();
builder.name((String) settings.get("name"));
builder.gameVersion(((RemoteVersion<?>) settings.get("game")).getGameVersion());
builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion());
if (settings.containsKey("forge"))
builder.version((RemoteVersion<?>) settings.get("forge"));
builder.version((RemoteVersion) settings.get("forge"));
if (settings.containsKey("liteloader"))
builder.version((RemoteVersion<?>) settings.get("liteloader"));
builder.version((RemoteVersion) settings.get("liteloader"));
if (settings.containsKey("optifine"))
builder.version((RemoteVersion<?>) settings.get("optifine"));
builder.version((RemoteVersion) settings.get("optifine"));
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions());
}
@@ -85,16 +85,16 @@ public final class InstallerWizardProvider implements WizardProvider {
settings.put("success_message", i18n("install.success"));
settings.put("failure_message", i18n("install.failed"));
Task ret = Task.empty();
Task ret = Task.ofResult("version", () -> version);
if (settings.containsKey("forge"))
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("forge")));
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
if (settings.containsKey("liteloader"))
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("liteloader")));
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
if (settings.containsKey("optifine"))
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("optifine")));
ret = ret.then(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
return ret.then(profile.getRepository().refreshVersionsAsync());
}
@@ -74,7 +74,7 @@ public class InstallersPage extends StackPane implements WizardPage {
FXUtils.loadFXML(this, "/assets/fxml/download/installers.fxml");
String gameVersion = ((RemoteVersion<?>) controller.getSettings().get("game")).getGameVersion();
String gameVersion = ((RemoteVersion) controller.getSettings().get("game")).getGameVersion();
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
hasVersion.setMessage(i18n("install.new_game.already_exists"));
txtName.getValidators().add(hasVersion);
@@ -103,7 +103,7 @@ public class InstallersPage extends StackPane implements WizardPage {
}
private String getVersion(String id) {
return ((RemoteVersion<?>) controller.getSettings().get(id)).getSelfVersion();
return ((RemoteVersion) controller.getSettings().get(id)).getSelfVersion();
}
@Override
@@ -27,9 +27,8 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.download.game.GameRemoteVersionTag;
import org.jackhuang.hmcl.download.game.GameVersionList;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
@@ -79,7 +78,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
if (versionList instanceof GameVersionList) {
if (versionList.hasType()) {
centrePane.getChildren().setAll(checkPane, list);
} else
centrePane.getChildren().setAll(list);
@@ -99,21 +98,19 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
}
private List<VersionsPageItem> loadVersions() {
boolean isGameVersionList = versionList instanceof GameVersionList;
return versionList.getVersions(gameVersion).stream()
.filter(it -> {
if (isGameVersionList)
switch (((GameRemoteVersionTag) it.getTag()).getType()) {
case RELEASE:
return chkRelease.isSelected();
case SNAPSHOT:
return chkSnapshot.isSelected();
default:
return chkOld.isSelected();
}
else return true;
switch (it.getVersionType()) {
case RELEASE:
return chkRelease.isSelected();
case SNAPSHOT:
return chkSnapshot.isSelected();
case OLD:
return chkOld.isSelected();
default:
return true;
}
})
.filter(Objects::nonNull)
.sorted()
.map(VersionsPageItem::new).collect(Collectors.toList());
}
@@ -25,8 +25,9 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.game.GameRemoteVersionTag;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersionTag;
import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
import org.jackhuang.hmcl.ui.FXUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -37,7 +38,7 @@ import java.util.Objects;
* @author huangyuhui
*/
public final class VersionsPageItem extends StackPane {
private final RemoteVersion<?> remoteVersion;
private final RemoteVersion remoteVersion;
@FXML
private Label lblSelfVersion;
@FXML
@@ -49,14 +50,14 @@ public final class VersionsPageItem extends StackPane {
@FXML
private StackPane imageViewContainer;
public VersionsPageItem(RemoteVersion<?> remoteVersion) {
public VersionsPageItem(RemoteVersion remoteVersion) {
this.remoteVersion = Objects.requireNonNull(remoteVersion);
FXUtils.loadFXML(this, "/assets/fxml/download/versions-list-item.fxml");
lblSelfVersion.setText(remoteVersion.getSelfVersion());
if (remoteVersion.getTag() instanceof GameRemoteVersionTag) {
switch (((GameRemoteVersionTag) remoteVersion.getTag()).getType()) {
if (remoteVersion instanceof GameRemoteVersion) {
switch (remoteVersion.getVersionType()) {
case RELEASE:
lblGameVersion.setText(i18n("version.game.release"));
imageView.setImage(new Image("/assets/img/icon.png", 32, 32, false, true));
@@ -70,13 +71,13 @@ public final class VersionsPageItem extends StackPane {
imageView.setImage(new Image("/assets/img/grass.png", 32, 32, false, true));
break;
}
} else if (remoteVersion.getTag() instanceof LiteLoaderRemoteVersionTag) {
} else if (remoteVersion instanceof LiteLoaderRemoteVersion) {
imageView.setImage(new Image("/assets/img/chicken.png", 32, 32, false, true));
lblGameVersion.setText(remoteVersion.getGameVersion());
} else if (remoteVersion instanceof OptiFineRemoteVersion) {
// optifine has no icon.
lblGameVersion.setText(remoteVersion.getGameVersion());
} else { // forge
} else if (remoteVersion instanceof ForgeRemoteVersion) {
imageView.setImage(new Image("/assets/img/forge.png", 32, 32, false, true));
lblGameVersion.setText(remoteVersion.getGameVersion());
}
@@ -84,7 +85,7 @@ public final class VersionsPageItem extends StackPane {
leftPane.getChildren().remove(imageViewContainer);
}
public RemoteVersion<?> getRemoteVersion() {
public RemoteVersion getRemoteVersion() {
return remoteVersion;
}
}
@@ -1,11 +1,4 @@
/*
* Apply google Roboto font to all UI components
* #5264AE
*/
.root {
-fx-base-font-family: "微软雅黑", "Microsoft YaHei", "Roboto";
-fx-font-family: -fx-base-font-family;
}
.disabled Label {
@@ -63,7 +63,7 @@
<ComponentList title="%settings.launcher.proxy"> <!-- proxy -->
<VBox spacing="10">
<JFXCheckBox fx:id="chkEnableProxy" text="%settings.launcher.proxy.enable" />
<JFXCheckBox fx:id="chkDisableProxy" text="%settings.launcher.proxy.disable" />
<VBox fx:id="proxyPane" style="-fx-padding: 0 0 0 30;">
<HBox>
<JFXRadioButton fx:id="chkProxyHttp" text="%settings.launcher.proxy.http" />
@@ -293,7 +293,7 @@ settings.launcher.language=Language
settings.launcher.log_font=Log Font
settings.launcher.proxy=Proxy
settings.launcher.proxy.authentication=Proxy Authentication
settings.launcher.proxy.enable=Enable Proxy
settings.launcher.proxy.disable=Use system proxies
settings.launcher.proxy.host=Host
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.password=Password
@@ -334,7 +334,9 @@ version.launch_script.save=Save the launch script
version.launch_script.success=Finished script creation, %s.
version.manage.redownload_assets_index=Redownload Assets Index
version.manage.remove=Delete this game
version.manage.remove.confirm=Sure to remove version %s?
version.manage.remove.confirm=Sure to remove game %s? You cannot restore this game again!
version.manage.remove.confirm.trash=Sure to remove game %s? You can restore the game with the name of %s in your system trash.
version.manage.remove.confirm.independent=Since this game is in independent mode, deleting this game will result in all saved worlds belonging to this game removed simultaneously. Sure to remove game %s?
version.manage.remove_libraries=Delete library files
version.manage.rename=Rename this game
version.manage.rename.message=Please enter the new name
@@ -293,7 +293,7 @@ settings.launcher.language=語言
settings.launcher.log_font=日誌字體
settings.launcher.proxy=代理
settings.launcher.proxy.authentication=身份驗證
settings.launcher.proxy.enable=启用代理
settings.launcher.proxy.disable=使用系統代理
settings.launcher.proxy.host=主機
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.password=密碼
@@ -334,7 +334,9 @@ version.launch_script.save=保存啓動腳本
version.launch_script.success=啓動腳本已生成完畢:%s
version.manage.redownload_assets_index=重新下載資源配置(assets_index.json)
version.manage.remove=刪除該版本
version.manage.remove.confirm=真的要刪除版本 %s 嗎?
version.manage.remove.confirm=真的要刪除版本 %s 嗎?你將無法找回被刪除的文件!
version.manage.remove.confirm.trash=真的要刪除版本 %s 嗎?你可以在系統的回收站中恢復文件夾 %s 來找回該版本。
version.manage.remove.confirm.independent=由於該遊戲使用了版本隔離,所以刪除該版本將導致該遊戲的存檔等數據一同被刪除,真的要刪除版本 %s 嗎?
version.manage.remove_libraries=刪除所有庫文件
version.manage.rename=重命名該版本
version.manage.rename.message=請輸入要改成的名字
@@ -293,7 +293,7 @@ settings.launcher.language=语言
settings.launcher.log_font=日志字体
settings.launcher.proxy=代理
settings.launcher.proxy.authentication=身份验证
settings.launcher.proxy.enable=启用代理
settings.launcher.proxy.disable=使用系统代理
settings.launcher.proxy.host=主机
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.password=密码
@@ -334,7 +334,9 @@ version.launch_script.save=保存启动脚本
version.launch_script.success=启动脚本已生成完毕:%s
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
version.manage.remove=删除该版本
version.manage.remove.confirm=真的要删除版本 %s 吗?
version.manage.remove.confirm=真的要删除版本 %s 吗?你将无法找回被删除的文件!
version.manage.remove.confirm.trash=真的要删除版本 %s 吗?你可以在系统的回收站中恢复文件夹 %s 来找回该版本。
version.manage.remove.confirm.independent=由于该游戏使用了版本隔离,所以删除该版本将导致该游戏的存档等数据一同被删除,真的要删除版本 %s 吗?
version.manage.remove_libraries=删除所有库文件
version.manage.rename=重命名该版本
version.manage.rename.message=请输入要改成的名字
@@ -64,8 +64,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
return baseURL
.replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
.replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com")
.replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://bmclapi2.bangbang93.com/maven")
.replace("https://libraries.minecraft.net", "http://bmclapi2.bangbang93.com/libraries")
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "http://bmclapi2.bangbang93.com/maven")
.replace("http://dl.liteloader.com/versions/versions.json", "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven")
.replace("https://authlib-injector.yushi.moe", "https://bmclapi2.bangbang93.com/mirrors/authlib-injector");
@@ -28,6 +28,8 @@ import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.ExceptionalFunction;
/**
* Note: This class has no state.
@@ -77,7 +79,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
}
@Override
public Task installLibraryAsync(Version version, RemoteVersion<?> libraryVersion) {
public Task installLibraryAsync(Version version, RemoteVersion libraryVersion) {
if (libraryVersion instanceof ForgeRemoteVersion)
return new ForgeInstallTask(this, version, (ForgeRemoteVersion) libraryVersion)
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
@@ -97,4 +99,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized.");
}
public ExceptionalFunction<AutoTypingMap<String>, Task, ?> installLibraryAsync(RemoteVersion libraryVersion) {
return var -> installLibraryAsync(var.get("version"), libraryVersion);
}
}
@@ -67,8 +67,8 @@ public class DefaultGameBuilder extends GameBuilder {
if (toolVersions.containsKey("optifine"))
result = result.then(libraryTaskHelper(gameVersion, "optifine"));
for (RemoteVersion<?> remoteVersion : remoteVersions)
result = result.then(var -> dependencyManager.installLibraryAsync(var.get("version"), remoteVersion));
for (RemoteVersion remoteVersion : remoteVersions)
result = result.then(dependencyManager.installLibraryAsync(remoteVersion));
return result;
}).finalized((variables, isDependentsSucceeded) -> {
@@ -67,7 +67,7 @@ public interface DependencyManager {
* @param libraryVersion the remote version of being installed library.
* @return the task to install the specific library.
*/
Task installLibraryAsync(Version version, RemoteVersion<?> libraryVersion);
Task installLibraryAsync(Version version, RemoteVersion libraryVersion);
/**
* Get registered version list.
@@ -31,7 +31,7 @@ public abstract class GameBuilder {
protected String name = "";
protected String gameVersion = "";
protected final Map<String, String> toolVersions = new HashMap<>();
protected final Set<RemoteVersion<?>> remoteVersions = new HashSet<>();
protected final Set<RemoteVersion> remoteVersions = new HashSet<>();
public String getName() {
return name;
@@ -64,7 +64,7 @@ public abstract class GameBuilder {
return this;
}
public GameBuilder version(RemoteVersion<?> remoteVersion) {
public GameBuilder version(RemoteVersion remoteVersion) {
remoteVersions.add(remoteVersion);
return this;
}
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.forge.ForgeVersionList;
import org.jackhuang.hmcl.download.game.GameVersionList;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
import org.jackhuang.hmcl.download.optifine.OptiFineVersionList;
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
/**
* @see <a href="http://wiki.vg">http://wiki,vg</a>
@@ -48,7 +48,7 @@ public class MojangDownloadProvider implements DownloadProvider {
case "liteloader":
return LiteLoaderVersionList.INSTANCE;
case "optifine":
return OptiFineVersionList.INSTANCE;
return OptiFineBMCLVersionList.INSTANCE;
default:
throw new IllegalArgumentException("Unrecognized version list id: " + id);
}
@@ -27,26 +27,36 @@ import java.util.Objects;
*
* @author huangyuhui
*/
public class RemoteVersion<T> implements Comparable<RemoteVersion<T>> {
public class RemoteVersion implements Comparable<RemoteVersion> {
private final String gameVersion;
private final String selfVersion;
private final String url;
private final T tag;
private final Type type;
/**
* Constructor.
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
* @param tag some necessary information for Installer Task.
* @param url the installer or universal jar URL.
*/
public RemoteVersion(String gameVersion, String selfVersion, String url, T tag) {
public RemoteVersion(String gameVersion, String selfVersion, String url) {
this(gameVersion, selfVersion, url, Type.UNCATEGORIZED);
}
/**
* Constructor.
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
*/
public RemoteVersion(String gameVersion, String selfVersion, String url, Type type) {
this.gameVersion = Objects.requireNonNull(gameVersion);
this.selfVersion = Objects.requireNonNull(selfVersion);
this.url = Objects.requireNonNull(url);
this.tag = tag;
this.type = Objects.requireNonNull(type);
}
public String getGameVersion() {
@@ -57,14 +67,14 @@ public class RemoteVersion<T> implements Comparable<RemoteVersion<T>> {
return selfVersion;
}
public T getTag() {
return tag;
}
public String getUrl() {
return url;
}
public Type getVersionType() {
return type;
}
@Override
public boolean equals(Object obj) {
return obj instanceof RemoteVersion && Objects.equals(selfVersion, ((RemoteVersion) obj).selfVersion);
@@ -80,13 +90,19 @@ public class RemoteVersion<T> implements Comparable<RemoteVersion<T>> {
return new ToStringBuilder(this)
.append("selfVersion", selfVersion)
.append("gameVersion", gameVersion)
.append("tag", tag)
.toString();
}
@Override
public int compareTo(RemoteVersion<T> o) {
public int compareTo(RemoteVersion o) {
// newer versions are smaller than older versions
return VersionNumber.asVersion(o.selfVersion).compareTo(VersionNumber.asVersion(selfVersion));
}
public enum Type {
UNCATEGORIZED,
RELEASE,
SNAPSHOT,
OLD
}
}
@@ -26,18 +26,18 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* The remote version list.
*
* @param <T> The type of {@code RemoteVersion<T>}, the type of tags.
* @param <T> The subclass of {@code RemoteVersion}, the type of RemoteVersion.
*
* @author huangyuhui
*/
public abstract class VersionList<T> {
public abstract class VersionList<T extends RemoteVersion> {
/**
* the remote version list.
* key: game version.
* values: corresponding remote versions.
*/
protected final SimpleMultimap<String, RemoteVersion<T>> versions = new SimpleMultimap<String, RemoteVersion<T>>(HashMap::new, TreeSet::new);
protected final SimpleMultimap<String, T> versions = new SimpleMultimap<String, T>(HashMap::new, TreeSet::new);
/**
* True if the version list has been loaded.
@@ -46,6 +46,8 @@ public abstract class VersionList<T> {
return !versions.isEmpty();
}
public abstract boolean hasType();
protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
/**
@@ -68,10 +70,10 @@ public abstract class VersionList<T> {
});
}
private Collection<RemoteVersion<T>> getVersionsImpl(String gameVersion) {
private Collection<T> getVersionsImpl(String gameVersion) {
lock.readLock().lock();
try {
Collection<RemoteVersion<T>> ans = versions.get(gameVersion);
Collection<T> ans = versions.get(gameVersion);
return ans.isEmpty() ? versions.values() : ans;
} finally {
lock.readLock().unlock();
@@ -84,7 +86,7 @@ public abstract class VersionList<T> {
* @param gameVersion the Minecraft version that remote versions belong to
* @return the collection of specific remote versions
*/
public final Collection<RemoteVersion<T>> getVersions(String gameVersion) {
public final Collection<T> getVersions(String gameVersion) {
return Collections.unmodifiableCollection(getVersionsImpl(gameVersion));
}
@@ -95,11 +97,11 @@ public abstract class VersionList<T> {
* @param remoteVersion the version of the remote version.
* @return the specific remote version, null if it is not found.
*/
public final Optional<RemoteVersion<T>> getVersion(String gameVersion, String remoteVersion) {
public final Optional<T> getVersion(String gameVersion, String remoteVersion) {
lock.readLock().lock();
try {
RemoteVersion<T> result = null;
for (RemoteVersion<T> it : versions.get(gameVersion))
T result = null;
for (T it : versions.get(gameVersion))
if (remoteVersion.equals(it.getSelfVersion()))
result = it;
return Optional.ofNullable(result);
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.RemoteVersion;
public class ForgeRemoteVersion extends RemoteVersion<Void> {
public class ForgeRemoteVersion extends RemoteVersion {
/**
* Constructor.
*
@@ -28,6 +28,6 @@ public class ForgeRemoteVersion extends RemoteVersion<Void> {
* @param url the installer or universal jar URL.
*/
public ForgeRemoteVersion(String gameVersion, String selfVersion, String url) {
super(gameVersion, selfVersion, url, null);
super(gameVersion, selfVersion, url);
}
}
@@ -32,13 +32,18 @@ import java.util.*;
*
* @author huangyuhui
*/
public final class ForgeVersionList extends VersionList<Void> {
public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
public static final ForgeVersionList INSTANCE = new ForgeVersionList();
private ForgeVersionList() {
}
@Override
public boolean hasType() {
return false;
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
final GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(FORGE_LIST)));
@@ -75,8 +75,6 @@ public final class GameAssetDownloadTask extends Task {
@Override
public void execute() throws Exception {
int size = refreshTask.getResult().size();
int downloaded = 0;
for (Map.Entry<File, AssetObject> entry : refreshTask.getResult()) {
if (Thread.interrupted())
throw new InterruptedException();
@@ -88,29 +86,11 @@ public final class GameAssetDownloadTask extends Task {
Logging.LOG.log(Level.SEVERE, "Unable to create new file " + file + ", because parent directory cannot be created");
continue;
}
if (file.isDirectory())
if (file.isDirectory() || file.isFile() && file.exists())
continue;
boolean flag = true;
try {
// check the checksum of file to ensure that the file is not need to re-download.
if (file.exists()) {
String sha1 = encodeHex(digest("SHA-1", FileUtils.readBytes(file)));
if (sha1.equals(assetObject.getHash())) {
++downloaded;
Logging.LOG.finest("File $file has been downloaded successfully, skipped downloading");
updateProgress(downloaded, size);
continue;
}
}
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Unable to get hash code of file " + file, e);
flag = !file.exists();
}
if (flag) {
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new IntegrityCheck("SHA-1", assetObject.getHash()));
task.setName(assetObject.getHash());
dependencies.add(task);
}
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new IntegrityCheck("SHA-1", assetObject.getHash()));
task.setName(assetObject.getHash());
dependencies.add(task);
}
}
@@ -17,12 +17,9 @@
*/
package org.jackhuang.hmcl.download.game;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.ReleaseType;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.Validation;
import org.jackhuang.hmcl.util.Immutable;
import java.util.Date;
@@ -30,64 +27,45 @@ import java.util.Date;
*
* @author huangyuhui
*/
public final class GameRemoteVersion implements Validation {
@Immutable
public final class GameRemoteVersion extends RemoteVersion {
@SerializedName("id")
private final String gameVersion;
@SerializedName("time")
private final ReleaseType type;
private final Date time;
@SerializedName("releaseTime")
private final Date releaseTime;
@SerializedName("type")
private final ReleaseType type;
@SerializedName("url")
private final String url;
public GameRemoteVersion() {
this("", new Date(), new Date(), ReleaseType.UNKNOWN);
}
public GameRemoteVersion(String gameVersion, Date time, Date releaseTime, ReleaseType type) {
this(gameVersion, time, releaseTime, type, Constants.DEFAULT_LIBRARY_URL + gameVersion + "/" + gameVersion + ".json");
}
public GameRemoteVersion(String gameVersion, Date time, Date releaseTime, ReleaseType type, String url) {
this.gameVersion = gameVersion;
this.time = time;
this.releaseTime = releaseTime;
public GameRemoteVersion(String gameVersion, String selfVersion, String url, ReleaseType type, Date time) {
super(gameVersion, selfVersion, url, getReleaseType(type));
this.type = type;
this.url = url;
}
public String getGameVersion() {
return gameVersion;
this.time = time;
}
public Date getTime() {
return time;
}
public Date getReleaseTime() {
return releaseTime;
}
public ReleaseType getType() {
return type;
}
public String getUrl() {
return url;
}
@Override
public void validate() throws JsonParseException {
if (StringUtils.isBlank(gameVersion))
throw new JsonParseException("GameRemoteVersion id cannot be blank");
if (StringUtils.isBlank(url))
throw new JsonParseException("GameRemoteVersion url cannot be blank");
public int compareTo(RemoteVersion o) {
if (!(o instanceof GameRemoteVersion))
return 0;
return ((GameRemoteVersion) o).getTime().compareTo(getTime());
}
private static Type getReleaseType(ReleaseType type) {
if (type == null) return Type.UNCATEGORIZED;
switch (type) {
case RELEASE:
return Type.RELEASE;
case SNAPSHOT:
return Type.SNAPSHOT;
case UNKNOWN:
return Type.UNCATEGORIZED;
default:
return Type.OLD;
}
}
}
@@ -0,0 +1,93 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.game.ReleaseType;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.Validation;
import java.util.Date;
/**
*
* @author huangyuhui
*/
public final class GameRemoteVersionInfo implements Validation {
@SerializedName("id")
private final String gameVersion;
@SerializedName("time")
private final Date time;
@SerializedName("releaseTime")
private final Date releaseTime;
@SerializedName("type")
private final ReleaseType type;
@SerializedName("url")
private final String url;
public GameRemoteVersionInfo() {
this("", new Date(), new Date(), ReleaseType.UNKNOWN);
}
public GameRemoteVersionInfo(String gameVersion, Date time, Date releaseTime, ReleaseType type) {
this(gameVersion, time, releaseTime, type, Constants.DEFAULT_LIBRARY_URL + gameVersion + "/" + gameVersion + ".json");
}
public GameRemoteVersionInfo(String gameVersion, Date time, Date releaseTime, ReleaseType type, String url) {
this.gameVersion = gameVersion;
this.time = time;
this.releaseTime = releaseTime;
this.type = type;
this.url = url;
}
public String getGameVersion() {
return gameVersion;
}
public Date getTime() {
return time;
}
public Date getReleaseTime() {
return releaseTime;
}
public ReleaseType getType() {
return type;
}
public String getUrl() {
return url;
}
@Override
public void validate() throws JsonParseException {
if (StringUtils.isBlank(gameVersion))
throw new JsonParseException("GameRemoteVersion id cannot be blank");
if (StringUtils.isBlank(url))
throw new JsonParseException("GameRemoteVersion url cannot be blank");
}
}
@@ -1,55 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.game.ReleaseType;
import org.jackhuang.hmcl.util.Immutable;
import java.util.Date;
/**
*
* @author huangyuhui
*/
@Immutable
public final class GameRemoteVersionTag {
private final ReleaseType type;
private final Date time;
/**
* No-arg constructor for Gson.
*/
@SuppressWarnings("unused")
public GameRemoteVersionTag() {
this(ReleaseType.UNKNOWN, new Date());
}
public GameRemoteVersionTag(ReleaseType type, Date time) {
this.type = type;
this.time = time;
}
public Date getTime() {
return time;
}
public ReleaseType getType() {
return type;
}
}
@@ -31,7 +31,7 @@ import java.util.List;
public final class GameRemoteVersions {
@SerializedName("versions")
private final List<GameRemoteVersion> versions;
private final List<GameRemoteVersionInfo> versions;
@SerializedName("latest")
private final GameRemoteLatestVersions latest;
@@ -44,7 +44,7 @@ public final class GameRemoteVersions {
this(Collections.emptyList(), null);
}
public GameRemoteVersions(List<GameRemoteVersion> versions, GameRemoteLatestVersions latest) {
public GameRemoteVersions(List<GameRemoteVersionInfo> versions, GameRemoteLatestVersions latest) {
this.versions = versions;
this.latest = latest;
}
@@ -53,7 +53,7 @@ public final class GameRemoteVersions {
return latest;
}
public List<GameRemoteVersion> getVersions() {
public List<GameRemoteVersionInfo> getVersions() {
return versions;
}
@@ -32,13 +32,18 @@ import java.util.Collections;
*
* @author huangyuhui
*/
public final class GameVersionList extends VersionList<GameRemoteVersionTag> {
public final class GameVersionList extends VersionList<GameRemoteVersion> {
public static final GameVersionList INSTANCE = new GameVersionList();
private GameVersionList() {
}
@Override
public boolean hasType() {
return true;
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.getVersionListURL()));
@@ -56,12 +61,12 @@ public final class GameVersionList extends VersionList<GameRemoteVersionTag> {
versions.clear();
GameRemoteVersions root = Constants.GSON.fromJson(task.getResult(), GameRemoteVersions.class);
for (GameRemoteVersion remoteVersion : root.getVersions()) {
versions.put(remoteVersion.getGameVersion(), new RemoteVersionGame(
for (GameRemoteVersionInfo remoteVersion : root.getVersions()) {
versions.put(remoteVersion.getGameVersion(), new GameRemoteVersion(
remoteVersion.getGameVersion(),
remoteVersion.getGameVersion(),
remoteVersion.getUrl(),
new GameRemoteVersionTag(remoteVersion.getType(), remoteVersion.getReleaseTime()))
remoteVersion.getType(), remoteVersion.getReleaseTime())
);
}
} finally {
@@ -70,15 +75,4 @@ public final class GameVersionList extends VersionList<GameRemoteVersionTag> {
}
};
}
private static class RemoteVersionGame extends RemoteVersion<GameRemoteVersionTag> {
public RemoteVersionGame(String gameVersion, String selfVersion, String url, GameRemoteVersionTag tag) {
super(gameVersion, selfVersion, url, tag);
}
@Override
public int compareTo(RemoteVersion<GameRemoteVersionTag> o) {
return o.getTag().getTime().compareTo(getTag().getTime());
}
}
}
@@ -12,7 +12,10 @@ import org.jackhuang.hmcl.util.NetworkUtils;
import org.tukaani.xz.XZInputStream;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
@@ -23,15 +26,15 @@ import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex;
public final class LibraryDownloadTask extends Task {
private final FileDownloadTask xzTask;
private final FileDownloadTask task;
private final LibraryDownloadTaskHelper helperTask;
private final File jar;
private final File xzFile;
private final Library library;
private boolean downloaded = false;
private boolean xz;
public LibraryDownloadTask(AbstractDependencyManager dependencyManager, File file, Library library) {
setSignificance(TaskSignificance.MODERATE);
if (library.is("net.minecraftforge", "forge"))
library = library.setClassifier("universal");
@@ -42,20 +45,12 @@ public final class LibraryDownloadTask extends Task {
xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz");
xzTask = new FileDownloadTask(NetworkUtils.toURL(url + ".pack.xz"),
xzFile, null, 1);
xzTask.setSignificance(TaskSignificance.MINOR);
setSignificance(TaskSignificance.MODERATE);
task = new FileDownloadTask(NetworkUtils.toURL(url),
file,
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
helperTask = new LibraryDownloadTaskHelper(file, url);
}
@Override
public Collection<? extends Task> getDependents() {
return library.getChecksums() != null ? Collections.singleton(xzTask) : Collections.singleton(task);
return Collections.singleton(helperTask);
}
@Override
@@ -66,32 +61,75 @@ public final class LibraryDownloadTask extends Task {
@Override
public void execute() throws Exception {
if (!isDependentsSucceeded()) {
// Since FileDownloadTask wraps the actual exception with another IOException.
// We should extract it letting the error message clearer.
Throwable t = library.getChecksums() != null ? xzTask.getLastException() : task.getLastException();
if (t.getCause() != null && t.getCause() != t)
throw new LibraryDownloadException(library, t.getCause());
else
throw new LibraryDownloadException(library, t);
if (helperTask.task == null) {
// NetworkUtils.URLExists failed.
throw new LibraryDownloadException(library, helperTask.getLastException());
} else {
// Since FileDownloadTask wraps the actual exception with another IOException.
// We should extract it letting the error message clearer.
Throwable t = helperTask.task.getLastException();
if (t.getCause() != null && t.getCause() != t)
throw new LibraryDownloadException(library, t.getCause());
else
throw new LibraryDownloadException(library, t);
}
} else {
if (xz) unpackLibrary(jar, FileUtils.readBytes(xzFile));
if (!checksumValid(jar, library.getChecksums())) {
jar.delete();
throw new IOException("Checksum failed for " + library);
}
}
}
private class LibraryDownloadTaskHelper extends Task {
private FileDownloadTask task;
private final File file;
private final String url;
public LibraryDownloadTaskHelper(File file, String url) {
this.file = file;
this.url = url;
setName(library.getName());
}
if (library.getChecksums() != null) {
unpackLibrary(jar, FileUtils.readBytes(xzFile));
if (!checksumValid(jar, library.getChecksums()))
throw new IOException("Checksum failed for " + library);
@Override
public boolean isRelyingOnDependencies() {
return true;
}
@Override
public Collection<? extends Task> getDependencies() {
return Collections.singleton(task);
}
@Override
public void execute() throws Exception {
URL packXz = NetworkUtils.toURL(url + ".pack.xz");
if (NetworkUtils.URLExists(packXz)) {
task = new FileDownloadTask(packXz, xzFile, null);
xz = true;
} else {
task = new FileDownloadTask(NetworkUtils.toURL(url),
file,
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
xz = false;
}
}
}
private static boolean checksumValid(File libPath, List<String> checksums) {
try {
if ((checksums == null) || (checksums.isEmpty())) {
if (checksums == null || checksums.isEmpty()) {
return true;
}
byte[] fileData = FileUtils.readBytes(libPath);
boolean valid = checksums.contains(encodeHex(digest("SHA-1", fileData)));
if ((!valid) && (libPath.getName().endsWith(".jar"))) {
if (!valid && libPath.getName().endsWith(".jar")) {
valid = validateJar(fileData, checksums);
}
return validateJar(fileData, checksums);
return valid;
} catch (IOException e) {
e.printStackTrace();
}
@@ -104,7 +142,7 @@ public final class LibraryDownloadTask extends Task {
JarInputStream jar = new JarInputStream(new ByteArrayInputStream(data));
JarEntry entry = jar.getNextJarEntry();
while (entry != null) {
byte[] eData = IOUtils.readFullyAsByteArray(jar);
byte[] eData = IOUtils.readFullyWithoutClosing(jar);
if (entry.getName().equals("checksums.sha1")) {
hashes = new String(eData, Charset.forName("UTF-8")).split("\n");
}
@@ -115,8 +153,8 @@ public final class LibraryDownloadTask extends Task {
}
jar.close();
if (hashes != null) {
boolean passed = !checksums.contains(files.get("checksums.sha1"));
if (passed) {
boolean failed = !checksums.contains(files.get("checksums.sha1"));
if (!failed) {
for (String hash : hashes) {
if ((!hash.trim().equals("")) && (hash.contains(" "))) {
String[] e = hash.split(" ");
@@ -125,17 +163,17 @@ public final class LibraryDownloadTask extends Task {
String checksum = files.get(target);
if ((!files.containsKey(target)) || (checksum == null)) {
Logging.LOG.warning(" " + target + " : missing");
passed = false;
failed = true;
break;
} else if (!checksum.equals(validChecksum)) {
Logging.LOG.warning(" " + target + " : failed (" + checksum + ", " + validChecksum + ")");
passed = false;
failed = true;
break;
}
}
}
}
return passed;
return !failed;
}
return false;
}
@@ -62,7 +62,7 @@ public final class VersionJsonDownloadTask extends Task {
@Override
public void execute() {
RemoteVersion<?> remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
RemoteVersion remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot find specific version " + gameVersion + " in remote repository"));
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl());
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL), ID));
@@ -39,13 +39,18 @@ import java.util.Optional;
*
* @author huangyuhui
*/
public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemoteVersionTag> {
public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemoteVersion> {
public static final LiteLoaderBMCLVersionList INSTANCE = new LiteLoaderBMCLVersionList();
private LiteLoaderBMCLVersionList() {
}
@Override
public boolean hasType() {
return false;
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
@@ -74,7 +74,7 @@ public final class LiteLoaderInstallTask extends TaskResult<Version> {
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()))
);
Version tempVersion = version.setLibraries(Lang.merge(remote.getTag().getLibraries(), Collections.singleton(library)));
Version tempVersion = version.setLibraries(Lang.merge(remote.getLibraries(), Collections.singleton(library)));
String mcArg = version.getMinecraftArguments().orElse("");
if (mcArg.contains("--tweakClass optifine.OptiFineTweaker"))
@@ -84,7 +84,7 @@ public final class LiteLoaderInstallTask extends TaskResult<Version> {
.setMainClass("net.minecraft.launchwrapper.Launch")
.setLibraries(Lang.merge(tempVersion.getLibraries(), version.getLibraries()))
.setLogging(Collections.emptyMap())
.setMinecraftArguments(mcArg + " --tweakClass " + remote.getTag().getTweakClass())
.setMinecraftArguments(mcArg + " --tweakClass " + remote.getTweakClass())
//.setArguments(Arguments.addGameArguments(Lang.get(version.getArguments()), "--tweakClass", remote.getTag().getTweakClass()))
);
@@ -22,7 +22,9 @@ import org.jackhuang.hmcl.game.Library;
import java.util.Collection;
public class LiteLoaderRemoteVersion extends RemoteVersion<LiteLoaderRemoteVersionTag> {
public class LiteLoaderRemoteVersion extends RemoteVersion {
private final String tweakClass;
private final Collection<Library> libraries;
/**
* Constructor.
*
@@ -31,6 +33,18 @@ public class LiteLoaderRemoteVersion extends RemoteVersion<LiteLoaderRemoteVersi
* @param url the installer or universal jar URL.
*/
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, String url, String tweakClass, Collection<Library> libraries) {
super(gameVersion, selfVersion, url, new LiteLoaderRemoteVersionTag(tweakClass, libraries));
super(gameVersion, selfVersion, url);
this.tweakClass = tweakClass;
this.libraries = libraries;
}
public Collection<Library> getLibraries() {
return libraries;
}
public String getTweakClass() {
return tweakClass;
}
}
@@ -1,54 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.game.Library;
import java.util.Collection;
import java.util.Collections;
/**
*
* @author huangyuhui
*/
public final class LiteLoaderRemoteVersionTag {
private final String tweakClass;
private final Collection<Library> libraries;
/**
* No-arg constructor for Gson.
*/
@SuppressWarnings("unused")
public LiteLoaderRemoteVersionTag() {
this("", Collections.emptySet());
}
public LiteLoaderRemoteVersionTag(String tweakClass, Collection<Library> libraries) {
this.tweakClass = tweakClass;
this.libraries = libraries;
}
public Collection<Library> getLibraries() {
return libraries;
}
public String getTweakClass() {
return tweakClass;
}
}
@@ -39,13 +39,18 @@ import java.util.Optional;
*
* @author huangyuhui
*/
public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVersionTag> {
public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVersion> {
public static final LiteLoaderVersionList INSTANCE = new LiteLoaderVersionList();
private LiteLoaderVersionList() {
}
@Override
public boolean hasType() {
return false;
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
@@ -33,13 +33,18 @@ import java.util.*;
*
* @author huangyuhui
*/
public final class OptiFineBMCLVersionList extends VersionList<Void> {
public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVersion> {
public static final OptiFineBMCLVersionList INSTANCE = new OptiFineBMCLVersionList();
private OptiFineBMCLVersionList() {
}
@Override
public boolean hasType() {
return true;
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi2.bangbang93.com/optifine/versionlist"));
@@ -57,16 +62,16 @@ public final class OptiFineBMCLVersionList extends VersionList<Void> {
}.getType());
for (OptiFineVersion element : root) {
String version = element.getType() + "_" + element.getPatch();
if (element.getType() == null || "pre".equals(element.getPatch()))
continue;
String mirror = "http://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch();
if (!duplicates.add(mirror))
continue;
boolean isPre = element.getPatch() != null && (element.getPatch().equals("pre") || element.getPatch().startsWith("alpha"));
if (StringUtils.isBlank(element.getGameVersion()))
continue;
VersionNumber.parseVersion(element.getGameVersion())
.ifPresent(gameVersion -> versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror)));
.ifPresent(gameVersion -> versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror, isPre)));
}
}
};
@@ -21,11 +21,11 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import java.util.function.Supplier;
public class OptiFineRemoteVersion extends RemoteVersion<Void> {
public class OptiFineRemoteVersion extends RemoteVersion {
private final Supplier<String> url;
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url) {
super(gameVersion, selfVersion, "", null);
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url, boolean snapshot) {
super(gameVersion, selfVersion, "", snapshot ? Type.SNAPSHOT : Type.RELEASE);
this.url = url;
}
@@ -1,121 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.optifine;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author huangyuhui
*/
public final class OptiFineVersionList extends VersionList<Void> {
private static final Pattern PATTERN = Pattern.compile("OptiFine (.*?) ");
private static final Pattern LINK_PATTERN = Pattern.compile("\"downloadx\\?f=OptiFine(.*)\"");
public static final OptiFineVersionList INSTANCE = new OptiFineVersionList();
private OptiFineVersionList() {
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL("https://optifine.net/downloads"));
return new Task() {
@Override
public Collection<Task> getDependents() {
return Collections.singleton(task);
}
@Override
public void execute() throws Exception {
lock.writeLock().lock();
try {
versions.clear();
String html = task.getResult().replace("&nbsp;", " ").replace("&gt;", ">").replace("&lt;", "<").replace("<br>", "<br />");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder db = factory.newDocumentBuilder();
Document doc = db.parse(new ByteArrayInputStream(html.getBytes("UTF-8")));
Element r = doc.getDocumentElement();
NodeList tables = r.getElementsByTagName("table");
for (int i = 0; i < tables.getLength(); i++) {
Element e = (Element) tables.item(i);
if ("downloadTable".equals(e.getAttribute("class"))) {
NodeList tr = e.getElementsByTagName("tr");
for (int k = 0; k < tr.getLength(); k++) {
NodeList downloadLine = ((Element) tr.item(k)).getElementsByTagName("td");
String url = null, version = null, gameVersion = null;
for (int j = 0; j < downloadLine.getLength(); j++) {
Element td = (Element) downloadLine.item(j);
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineMirror"))
url = ((Element) td.getElementsByTagName("a").item(0)).getAttribute("href");
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineFile"))
version = td.getTextContent();
}
if (version == null || url == null)
continue;
Matcher matcher = PATTERN.matcher(version);
while (matcher.find())
gameVersion = matcher.group(1);
if (gameVersion == null)
continue;
String finalURL = url;
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Lang.hideException(() -> getLink(finalURL))));
}
}
}
} finally {
lock.writeLock().unlock();
}
}
};
}
private static String getLink(String url) throws IOException {
String result = null;
String content = NetworkUtils.doGet(NetworkUtils.toURL(url));
Matcher m = LINK_PATTERN.matcher(content);
while (m.find())
result = m.group(1);
if (result == null)
throw new IllegalStateException("Cannot find version in " + content);
return "https://optifine.net/downloadx?f=OptiFine" + result;
}
}
@@ -45,12 +45,4 @@ public class ProcessStoppedEvent extends Event {
public ManagedProcess getProcess() {
return process;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("source", source)
.append("process", process)
.toString();
}
}
@@ -160,14 +160,18 @@ public class DefaultGameRepository implements GameRepository {
if (!file.renameTo(removedFile))
return false;
versions.remove(id);
if (FileUtils.isMovingToTrashSupported()) {
return FileUtils.moveToTrash(removedFile);
}
// remove json files first to ensure HMCL will not recognize this folder as a valid version.
List<File> jsons = FileUtils.listFilesByExtension(removedFile, "json");
jsons.forEach(f -> {
if (!f.delete())
Logging.LOG.warning("Unable to delete file " + f);
});
versions.remove(id);
// remove the version from version list regardless of whether the directory was removed successfully or not.
try {
FileUtils.deleteDirectory(removedFile);
@@ -223,6 +223,7 @@ public class Library implements Comparable<Library> {
obj.addProperty("name", src.getName());
obj.addProperty("url", src.url);
obj.add("downloads", context.serialize(src.downloads));
obj.add("checksums", context.serialize(src.getChecksums()));
obj.add("extract", context.serialize(src.extract));
obj.add("natives", context.serialize(src.natives, new TypeToken<Map<OperatingSystem, String>>() {
}.getType()));
@@ -151,8 +151,9 @@ public class Version implements Comparable<Version>, Validation {
}
protected Version resolve(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {
if (inheritsFrom == null)
return this;
if (inheritsFrom == null) {
return this.jar == null ? this.setJar(id) : this;
}
// To maximize the compatibility.
if (!resolvedSoFar.add(id)) {
@@ -258,7 +258,7 @@ public class DefaultLauncher extends Launcher {
File nativeFolder = Files.createTempDirectory("minecraft").toFile();
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
List<String> rawCommandLine = generateCommandLine(nativeFolder, true).asList();
List<String> rawCommandLine = generateCommandLine(nativeFolder, isEnablingLoggingInfo()).asList();
decompressNatives(nativeFolder);
@@ -316,14 +316,17 @@ public class DefaultLauncher extends Launcher {
throw new PermissionException();
}
protected boolean isEnablingLoggingInfo() {
return version.getLogging() != null && version.getLogging().containsKey(DownloadType.CLIENT)
&& !"net.minecraft.launchwrapper.Launch".equals(version.getMainClass());
}
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener) {
startMonitors(managedProcess, processListener, true);
}
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
boolean enablesLoggingInfo = version.getLogging() != null && version.getLogging().containsKey(DownloadType.CLIENT)
&& !"net.minecraft.launchwrapper.Launch".equals(version.getMainClass());
if (enablesLoggingInfo)
if (isEnablingLoggingInfo())
startMonitorsWithLoggingInfo(managedProcess, processListener, isDaemon);
else
startMonitorsWithoutLoggingInfo(managedProcess, processListener, isDaemon);
@@ -152,14 +152,20 @@ public final class ModInfo implements Comparable<ModInfo> {
try {
return ForgeModMetadata.fromFile(modFile);
} catch (Exception ignore) {
description = "May be Forge mod";
}
try {
return RiftModMetadata.fromFile(modFile);
} catch (Exception ignore) {
}
description = "";
break;
case "litemod":
try {
return LiteModMetadata.fromFile(modFile);
} catch (Exception ignore) {
description = "May be LiteLoader mod";
description = "LiteLoader Mod";
}
break;
default:
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod;
import org.jackhuang.hmcl.util.CompressingUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.io.IOException;
@@ -268,7 +269,7 @@ public final class MultiMCInstanceConfiguration {
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modpackFile.toPath())) {
Path root = Files.list(fs.getPath("/")).filter(Files::isDirectory).findAny()
.orElseThrow(() -> new IOException("Not a valid MultiMC modpack"));
String name = root.normalize().getFileName().toString();
String name = StringUtils.removeSuffix(root.normalize().getFileName().toString(), "/");
Path instancePath = root.resolve("instance.cfg");
if (Files.notExists(instancePath))
@@ -120,7 +120,7 @@ public final class MultiMCModpackInstallTask extends Task {
if (Files.exists(patches))
for (Path patchJson : Files.newDirectoryStream(patches)) {
if (patchJson.endsWith(".json")) {
if (patchJson.toString().endsWith(".json")) {
MultiMCInstancePatch patch = Constants.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(patchJson)), MultiMCInstancePatch.class);
List<String> newArguments = new LinkedList<>();
for (String arg : patch.getTweakers()) {
@@ -0,0 +1,70 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.mod;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
@Immutable
public final class RiftModMetadata {
private final String id;
private final String name;
private final List<String> authors;
public RiftModMetadata() {
this("", "", Collections.emptyList());
}
public RiftModMetadata(String id, String name, List<String> authors) {
this.id = id;
this.name = name;
this.authors = authors;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public List<String> getAuthors() {
return authors;
}
public static ModInfo fromFile(File modFile) throws IOException, JsonParseException {
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) {
Path mcmod = fs.getPath("riftmod.json");
if (Files.notExists(mcmod))
throw new IOException("File " + modFile + " is not a Forge mod.");
RiftModMetadata metadata = JsonUtils.fromNonNullJson(IOUtils.readFullyAsString(Files.newInputStream(mcmod)), RiftModMetadata.class);
String authors = metadata.getAuthors() == null ? "" : String.join(", ", metadata.getAuthors());
return new ModInfo(modFile, metadata.getName(), "",
authors, "", "", "");
}
}
}
@@ -108,7 +108,7 @@ public abstract class Task {
return true;
}
private String name = getClass().toString();
private String name = getClass().getName();
public String getName() {
return name;
@@ -41,6 +41,12 @@ public final class CommandBuilder {
}
}
/**
* Parsing will ignore your manual escaping
*
* @param args commands
* @return this
*/
public CommandBuilder add(String... args) {
for (String s : args)
raw.add(new Item(s, true));
@@ -89,35 +95,27 @@ public final class CommandBuilder {
}
private static String parseWindows(String s) {
if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0)
if (s.charAt(0) != '"') {
// The argument has not been quoted, add quotes.
return '"' + s.replace("\\", "\\\\").replace("\"", "\"\"") + '"';
} else if (s.endsWith("\"")) {
// The argument has already been quoted.
return s;
} else {
// Unmatched quote for the argument.
throw new IllegalArgumentException();
}
String escape = " \t\"^&<>|";
if (StringUtils.containsOne(s, escape.toCharArray()))
// The argument has not been quoted, add quotes.
return '"' + s
.replace("\\", "\\\\")
.replace("\"", "\"\"")
+ '"';
else {
return s;
}
}
private static String parseBash(String s) {
if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0)
if (s.charAt(0) != '"') {
// The argument has not been quoted, add quotes.
return '"' + s.replace("\"", "\\\"") + '"';
} else if (s.endsWith("\"")) {
// The argument has already been quoted.
return s;
} else {
// Unmatched quote for the argument.
throw new IllegalArgumentException();
}
else
String escaping = " \t\"!#$&'()*,;<=>?[\\]^`{|}~";
String escaped = "\"$&`";
if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0 || StringUtils.containsOne(s, escaping.toCharArray())) {
// The argument has not been quoted, add quotes.
for (char ch : escaped.toCharArray())
s = s.replace("" + ch, "\\" + ch);
return '"' + s + '"';
} else
return s;
}
}
@@ -26,6 +26,8 @@ import java.nio.file.spi.FileSystemProvider;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.zip.ZipError;
import java.util.zip.ZipException;
/**
* Utilities of compressing
@@ -66,7 +68,12 @@ public final class CompressingUtils {
env.put("encoding", encoding);
if (useTempFile)
env.put("useTempFile", true);
return ZIPFS_PROVIDER.newFileSystem(zipFile, env);
try {
return ZIPFS_PROVIDER.newFileSystem(zipFile, env);
} catch (ZipError error) {
// Since Java 8 throws ZipError stupidly
throw new ZipException(error.getMessage());
}
}
/**
@@ -20,8 +20,10 @@ package org.jackhuang.hmcl.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@@ -95,6 +97,30 @@ public final class FileUtils {
return Lang.test(() -> deleteDirectory(directory));
}
public static boolean moveToTrash(File file) {
try {
java.awt.Desktop desktop = java.awt.Desktop.getDesktop();
Method moveToTrash = desktop.getClass().getMethod("moveToTrash", File.class);
moveToTrash.invoke(desktop, file);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Check if {@code java.awt.Desktop.moveToTrash} exists.
* @return true if the method exists.
*/
public static boolean isMovingToTrashSupported() {
try {
java.awt.Desktop.class.getMethod("moveToTrash", File.class);
return true;
} catch (ReflectiveOperationException e) {
return false;
}
}
public static void cleanDirectory(File directory)
throws IOException {
if (!directory.exists()) {
@@ -31,6 +31,12 @@ public final class IOUtils {
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
public static byte[] readFullyWithoutClosing(InputStream stream) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
copyTo(stream, result);
return result.toByteArray();
}
public static ByteArrayOutputStream readFully(InputStream stream) throws IOException {
try (InputStream is = stream) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
@@ -17,10 +17,7 @@
*/
package org.jackhuang.hmcl.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@@ -156,4 +153,12 @@ public final class NetworkUtils {
return false;
}
}
public static boolean URLExists(URL url) throws IOException {
try (InputStream stream = url.openStream()) {
return true;
} catch (FileNotFoundException e) {
return false;
}
}
}
@@ -176,6 +176,13 @@ public final class StringUtils {
return false;
}
public static boolean containsOne(String pattern, char... targets) {
for (char target : targets)
if (pattern.toLowerCase().indexOf(Character.toLowerCase(target)) >= 0)
return true;
return false;
}
public static List<String> tokenize(String str) {
if (str == null)
return new LinkedList<>();
BIN
View File
Binary file not shown.