mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-15 06:45:42 -04:00
feat: search modrinth
This commit is contained in:
parent
f4c25003a0
commit
f088bfa114
@ -19,9 +19,15 @@ package org.jackhuang.hmcl.setting;
|
|||||||
|
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import org.jackhuang.hmcl.download.*;
|
import org.jackhuang.hmcl.download.*;
|
||||||
|
import org.jackhuang.hmcl.task.DownloadException;
|
||||||
import org.jackhuang.hmcl.task.FetchTask;
|
import org.jackhuang.hmcl.task.FetchTask;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
|
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||||
|
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -32,6 +38,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
|||||||
import static org.jackhuang.hmcl.task.FetchTask.DEFAULT_CONCURRENCY;
|
import static org.jackhuang.hmcl.task.FetchTask.DEFAULT_CONCURRENCY;
|
||||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class DownloadProviders {
|
public final class DownloadProviders {
|
||||||
private DownloadProviders() {}
|
private DownloadProviders() {}
|
||||||
@ -127,4 +134,23 @@ public final class DownloadProviders {
|
|||||||
public static DownloadProvider getDownloadProvider() {
|
public static DownloadProvider getDownloadProvider() {
|
||||||
return config().isAutoChooseDownloadType() ? currentDownloadProvider : fileDownloadProvider;
|
return config().isAutoChooseDownloadType() ? currentDownloadProvider : fileDownloadProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String localizeErrorMessage(Exception exception) {
|
||||||
|
if (exception instanceof DownloadException) {
|
||||||
|
URL url = ((DownloadException) exception).getUrl();
|
||||||
|
if (exception.getCause() instanceof SocketTimeoutException) {
|
||||||
|
return i18n("install.failed.downloading.timeout", url);
|
||||||
|
} else if (exception.getCause() instanceof ResponseCodeException) {
|
||||||
|
ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();
|
||||||
|
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
|
||||||
|
return i18n("download.code." + responseCodeException.getResponseCode(), url);
|
||||||
|
} else {
|
||||||
|
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringUtils.getStackTrace(exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import javafx.scene.Node;
|
|||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import org.jackhuang.hmcl.download.*;
|
import org.jackhuang.hmcl.download.*;
|
||||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
|||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
@ -143,19 +144,25 @@ public class DownloadPage extends BorderPane implements DecoratorPage {
|
|||||||
setCenter(transitionPane);
|
setCenter(transitionPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file, String subdirectoryName) {
|
private void download(Profile profile, @Nullable String version, DownloadManager.Version file, String subdirectoryName) {
|
||||||
if (version == null) version = profile.getSelectedVersion();
|
if (version == null) version = profile.getSelectedVersion();
|
||||||
|
|
||||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
|
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
|
||||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(file.getFileName());
|
Path dest = runDirectory.resolve(subdirectoryName).resolve(file.getFile().getFilename());
|
||||||
|
|
||||||
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
|
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
|
||||||
});
|
});
|
||||||
|
|
||||||
TaskExecutor executor = Task.composeAsync(() -> {
|
TaskExecutor executor = Task.composeAsync(() -> {
|
||||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getDownloadUrl()), dest.toFile());
|
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
|
||||||
task.setName(file.getDisplayName());
|
task.setName(file.getName());
|
||||||
return task;
|
return task;
|
||||||
|
}).whenComplete(exception -> {
|
||||||
|
if (exception != null) {
|
||||||
|
Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR);
|
||||||
|
} else {
|
||||||
|
Controllers.showToast(i18n("install.success"));
|
||||||
|
}
|
||||||
}).executor(false);
|
}).executor(false);
|
||||||
|
|
||||||
downloadingPane.setExecutor(executor, true);
|
downloadingPane.setExecutor(executor, true);
|
||||||
|
@ -25,6 +25,7 @@ import javafx.beans.binding.Bindings;
|
|||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
@ -37,27 +38,34 @@ import javafx.scene.image.Image;
|
|||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import org.jackhuang.hmcl.game.GameVersion;
|
import org.jackhuang.hmcl.game.GameVersion;
|
||||||
|
import org.jackhuang.hmcl.game.Version;
|
||||||
|
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||||
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
||||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||||
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||||
|
|
||||||
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
|
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
|
||||||
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||||
@ -65,10 +73,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
||||||
private final boolean versionSelection;
|
private final boolean versionSelection;
|
||||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||||
private final ListProperty<CurseAddon> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
private final ListProperty<DownloadManager.Mod> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||||
|
private final ObservableList<String> versions = FXCollections.observableArrayList();
|
||||||
|
private final StringProperty selectedVersion = new SimpleStringProperty();
|
||||||
private final DownloadPage.DownloadCallback callback;
|
private final DownloadPage.DownloadCallback callback;
|
||||||
private boolean searchInitialized = false;
|
private boolean searchInitialized = false;
|
||||||
protected final BooleanProperty supportChinese = new SimpleBooleanProperty();
|
protected final BooleanProperty supportChinese = new SimpleBooleanProperty();
|
||||||
|
protected final ListProperty<String> downloadSources = new SimpleListProperty<>(this, "downloadSources", FXCollections.observableArrayList());
|
||||||
|
protected final StringProperty downloadSource = new SimpleStringProperty();
|
||||||
|
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||||
|
private TaskExecutor executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.jackhuang.hmcl.mod.curse.CurseModManager#SECTION_MODPACK
|
* @see org.jackhuang.hmcl.mod.curse.CurseModManager#SECTION_MODPACK
|
||||||
@ -101,6 +115,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
searchInitialized = true;
|
searchInitialized = true;
|
||||||
search("", 0, 0, "", 0);
|
search("", 0, 0, "", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (versionSelection) {
|
||||||
|
versions.setAll(profile.getRepository().getVersions().stream()
|
||||||
|
.filter(v -> !v.isHidden())
|
||||||
|
.sorted(Comparator.comparing((Version v) -> v.getReleaseTime() == null ? new Date(0L) : v.getReleaseTime())
|
||||||
|
.thenComparing(v -> VersionNumber.asVersion(v.getId())))
|
||||||
|
.map(Version::getId)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
selectedVersion.set(profile.getSelectedVersion());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFailed() {
|
public boolean isFailed() {
|
||||||
@ -133,7 +157,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
||||||
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
|
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
|
||||||
: null;
|
: null;
|
||||||
Task.supplyAsync(() -> {
|
if (executor != null && !executor.isCancelled()) {
|
||||||
|
executor.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
executor = Task.supplyAsync(() -> {
|
||||||
String gameVersion;
|
String gameVersion;
|
||||||
if (StringUtils.isBlank(version.get().getVersion())) {
|
if (StringUtils.isBlank(version.get().getVersion())) {
|
||||||
gameVersion = userGameVersion;
|
gameVersion = userGameVersion;
|
||||||
@ -146,16 +174,32 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (exception == null) {
|
if (exception == null) {
|
||||||
items.setAll(result);
|
items.setAll(result.collect(Collectors.toList()));
|
||||||
failed.set(false);
|
failed.set(false);
|
||||||
} else {
|
} else {
|
||||||
failed.set(true);
|
failed.set(true);
|
||||||
}
|
}
|
||||||
}).start();
|
}).executor(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<CurseAddon> searchImpl(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
|
protected Stream<DownloadManager.Mod> searchImpl(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
|
||||||
return CurseModManager.searchPaginated(gameVersion, category, section, pageOffset, searchFilter, sort);
|
return CurseModManager.searchPaginated(gameVersion, category, section, pageOffset, searchFilter, sort).stream().map(CurseAddon::toMod);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getLocalizedCategory(String category) {
|
||||||
|
return i18n("curse.category." + category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getLocalizedOfficialPage() {
|
||||||
|
return i18n("mods.curseforge");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Profile.ProfileVersion getProfileVersion() {
|
||||||
|
if (versionSelection) {
|
||||||
|
return new Profile.ProfileVersion(version.get().getProfile(), selectedVersion.get());
|
||||||
|
} else {
|
||||||
|
return version.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -194,18 +238,28 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
{
|
{
|
||||||
int rowIndex = 0;
|
int rowIndex = 0;
|
||||||
|
|
||||||
if (control.versionSelection) {
|
if (control.versionSelection && control.downloadSources.getSize() > 1) {
|
||||||
JFXComboBox<String> versionsComboBox = new JFXComboBox<>();
|
JFXComboBox<String> versionsComboBox = new JFXComboBox<>();
|
||||||
GridPane.setColumnSpan(versionsComboBox, 3);
|
|
||||||
versionsComboBox.setMaxWidth(Double.MAX_VALUE);
|
versionsComboBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
Bindings.bindContent(versionsComboBox.getItems(), control.versions);
|
||||||
|
selectedItemPropertyFor(versionsComboBox).bindBidirectional(control.selectedVersion);
|
||||||
|
|
||||||
searchPane.addRow(rowIndex++, new Label(i18n("version")), versionsComboBox);
|
JFXComboBox<String> downloadSourceComboBox = new JFXComboBox<>();
|
||||||
|
downloadSourceComboBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
downloadSourceComboBox.getItems().setAll(control.downloadSources.get());
|
||||||
|
downloadSourceComboBox.setConverter(stringConverter(I18n::i18n));
|
||||||
|
selectedItemPropertyFor(downloadSourceComboBox).bindBidirectional(control.downloadSource);
|
||||||
|
|
||||||
|
searchPane.addRow(rowIndex++, new Label(i18n("version")), versionsComboBox, new Label(i18n("settings.launcher.download_source")), downloadSourceComboBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
JFXTextField nameField = new JFXTextField();
|
JFXTextField nameField = new JFXTextField();
|
||||||
nameField.setPromptText(getSkinnable().supportChinese.get() ? i18n("search.hint.chinese") : i18n("search.hint.english"));
|
nameField.setPromptText(getSkinnable().supportChinese.get() ? i18n("search.hint.chinese") : i18n("search.hint.english"));
|
||||||
|
|
||||||
JFXTextField gameVersionField = new JFXTextField();
|
JFXComboBox<String> gameVersionField = new JFXComboBox<>();
|
||||||
|
gameVersionField.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
gameVersionField.setEditable(true);
|
||||||
|
gameVersionField.getItems().setAll(DownloadManager.DEFAULT_GAME_VERSIONS);
|
||||||
Label lblGameVersion = new Label(i18n("world.game_version"));
|
Label lblGameVersion = new Label(i18n("world.game_version"));
|
||||||
searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField);
|
searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField);
|
||||||
|
|
||||||
@ -267,7 +321,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
searchPane.addRow(rowIndex++, searchBox);
|
searchPane.addRow(rowIndex++, searchBox);
|
||||||
|
|
||||||
EventHandler<ActionEvent> searchAction = e -> getSkinnable()
|
EventHandler<ActionEvent> searchAction = e -> getSkinnable()
|
||||||
.search(gameVersionField.getText(),
|
.search(gameVersionField.getSelectionModel().getSelectedItem(),
|
||||||
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
|
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
|
||||||
.map(CategoryIndented::getCategoryId)
|
.map(CategoryIndented::getCategoryId)
|
||||||
.orElse(0),
|
.orElse(0),
|
||||||
@ -293,16 +347,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
}
|
}
|
||||||
}, getSkinnable().failedProperty()));
|
}, getSkinnable().failedProperty()));
|
||||||
|
|
||||||
JFXListView<CurseAddon> listView = new JFXListView<>();
|
JFXListView<DownloadManager.Mod> listView = new JFXListView<>();
|
||||||
spinnerPane.setContent(listView);
|
spinnerPane.setContent(listView);
|
||||||
Bindings.bindContent(listView.getItems(), getSkinnable().items);
|
Bindings.bindContent(listView.getItems(), getSkinnable().items);
|
||||||
listView.setOnMouseClicked(e -> {
|
listView.setOnMouseClicked(e -> {
|
||||||
if (listView.getSelectionModel().getSelectedIndex() < 0)
|
if (listView.getSelectionModel().getSelectedIndex() < 0)
|
||||||
return;
|
return;
|
||||||
CurseAddon selectedItem = listView.getSelectionModel().getSelectedItem();
|
DownloadManager.Mod selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||||
Controllers.navigate(new DownloadPage(selectedItem, getSkinnable().version.get(), getSkinnable().callback));
|
Controllers.navigate(new DownloadPage(getSkinnable(), selectedItem, getSkinnable().getProfileVersion(), getSkinnable().callback));
|
||||||
});
|
});
|
||||||
listView.setCellFactory(x -> new FloatListCell<CurseAddon>(listView) {
|
listView.setCellFactory(x -> new FloatListCell<DownloadManager.Mod>(listView) {
|
||||||
TwoLineListItem content = new TwoLineListItem();
|
TwoLineListItem content = new TwoLineListItem();
|
||||||
ImageView imageView = new ImageView();
|
ImageView imageView = new ImageView();
|
||||||
|
|
||||||
@ -315,19 +369,17 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateControl(CurseAddon dataItem, boolean empty) {
|
protected void updateControl(DownloadManager.Mod dataItem, boolean empty) {
|
||||||
if (empty) return;
|
if (empty) return;
|
||||||
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(dataItem.getSlug());
|
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(dataItem.getSlug());
|
||||||
content.setTitle(mod != null ? mod.getDisplayName() : dataItem.getName());
|
content.setTitle(mod != null ? mod.getDisplayName() : dataItem.getTitle());
|
||||||
content.setSubtitle(dataItem.getSummary());
|
content.setSubtitle(dataItem.getDescription());
|
||||||
content.getTags().setAll(dataItem.getCategories().stream()
|
content.getTags().setAll(dataItem.getCategories().stream()
|
||||||
.map(category -> i18n("curse.category." + category.getCategoryId()))
|
.map(category -> getSkinnable().getLocalizedCategory(category))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
for (CurseAddon.Attachment attachment : dataItem.getAttachments()) {
|
if (StringUtils.isNotBlank(dataItem.getIconUrl())) {
|
||||||
if (attachment.isDefault()) {
|
imageView.setImage(new Image(dataItem.getIconUrl(), 40, 40, true, true, true));
|
||||||
imageView.setImage(new Image(attachment.getThumbnailUrl(), 40, 40, true, true, true));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -35,12 +35,12 @@ import javafx.scene.layout.Priority;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import org.jackhuang.hmcl.game.GameVersion;
|
import org.jackhuang.hmcl.game.GameVersion;
|
||||||
|
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||||
import org.jackhuang.hmcl.mod.ModManager;
|
import org.jackhuang.hmcl.mod.ModManager;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
@ -59,7 +59,6 @@ import java.time.ZoneId;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.FormatStyle;
|
import java.time.format.FormatStyle;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -68,15 +67,17 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
|||||||
|
|
||||||
public class DownloadPage extends Control implements DecoratorPage {
|
public class DownloadPage extends Control implements DecoratorPage {
|
||||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||||
private final ListProperty<CurseAddon.LatestFile> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
private final ListProperty<DownloadManager.Version> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||||
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
||||||
private final CurseAddon addon;
|
private final DownloadManager.Mod addon;
|
||||||
private final ModTranslations.Mod mod;
|
private final ModTranslations.Mod mod;
|
||||||
private final Profile.ProfileVersion version;
|
private final Profile.ProfileVersion version;
|
||||||
private final DownloadCallback callback;
|
private final DownloadCallback callback;
|
||||||
|
private final DownloadListPage page;
|
||||||
|
|
||||||
public DownloadPage(CurseAddon addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
|
public DownloadPage(DownloadListPage page, DownloadManager.Mod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
|
||||||
|
this.page = page;
|
||||||
this.addon = addon;
|
this.addon = addon;
|
||||||
this.mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
this.mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
||||||
this.version = version;
|
this.version = version;
|
||||||
@ -86,27 +87,33 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
? version.getProfile().getRepository().getVersionJar(version.getVersion())
|
? version.getProfile().getRepository().getVersionJar(version.getVersion())
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Task.runAsync(() -> {
|
setLoading(true);
|
||||||
|
setFailed(false);
|
||||||
|
Task.supplyAsync(() -> {
|
||||||
if (StringUtils.isNotBlank(version.getVersion())) {
|
if (StringUtils.isNotBlank(version.getVersion())) {
|
||||||
Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
||||||
if (gameVersion.isPresent()) {
|
if (gameVersion.isPresent()) {
|
||||||
List<CurseAddon.LatestFile> files = CurseModManager.getFiles(addon);
|
return addon.getData().loadVersions()
|
||||||
items.setAll(files.stream()
|
.filter(file -> file.getGameVersions().contains(gameVersion.get()));
|
||||||
.filter(file -> file.getGameVersion().contains(gameVersion.get()))
|
|
||||||
.sorted(Comparator.comparing(CurseAddon.LatestFile::getParsedFileDate).reversed())
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<CurseAddon.LatestFile> files = CurseModManager.getFiles(addon);
|
return addon.getData().loadVersions();
|
||||||
files.sort(Comparator.comparing(CurseAddon.LatestFile::getParsedFileDate).reversed());
|
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||||
items.setAll(files);
|
if (exception == null) {
|
||||||
|
items.setAll(result
|
||||||
|
.sorted(Comparator.comparing(DownloadManager.Version::getDatePublished).reversed())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
setFailed(false);
|
||||||
|
} else {
|
||||||
|
setFailed(true);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
this.state.set(State.fromTitle(addon.getName()));
|
this.state.set(State.fromTitle(addon.getTitle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurseAddon getAddon() {
|
public DownloadManager.Mod getAddon() {
|
||||||
return addon;
|
return addon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +145,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
this.failed.set(failed);
|
this.failed.set(failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void download(CurseAddon.LatestFile file) {
|
public void download(DownloadManager.Version file) {
|
||||||
if (this.callback == null) {
|
if (this.callback == null) {
|
||||||
saveAs(file);
|
saveAs(file);
|
||||||
} else {
|
} else {
|
||||||
@ -146,20 +153,20 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveAs(CurseAddon.LatestFile file) {
|
public void saveAs(DownloadManager.Version file) {
|
||||||
String extension = StringUtils.substringAfterLast(file.getFileName(), '.');
|
String extension = StringUtils.substringAfterLast(file.getFile().getFilename(), '.');
|
||||||
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
fileChooser.setTitle(i18n("button.save_as"));
|
fileChooser.setTitle(i18n("button.save_as"));
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("file"), "*." + extension));
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("file"), "*." + extension));
|
||||||
fileChooser.setInitialFileName(file.getFileName());
|
fileChooser.setInitialFileName(file.getFile().getFilename());
|
||||||
File dest = fileChooser.showSaveDialog(Controllers.getStage());
|
File dest = fileChooser.showSaveDialog(Controllers.getStage());
|
||||||
if (dest == null) {
|
if (dest == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Controllers.taskDialog(
|
Controllers.taskDialog(
|
||||||
new FileDownloadTask(NetworkUtils.toURL(file.getDownloadUrl()), dest).executor(true),
|
new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest).executor(true),
|
||||||
i18n("message.downloading")
|
i18n("message.downloading")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -188,20 +195,18 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
|
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
|
||||||
|
|
||||||
ImageView imageView = new ImageView();
|
ImageView imageView = new ImageView();
|
||||||
for (CurseAddon.Attachment attachment : getSkinnable().addon.getAttachments()) {
|
if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) {
|
||||||
if (attachment.isDefault()) {
|
imageView.setImage(new Image(getSkinnable().addon.getIconUrl(), 40, 40, true, true, true));
|
||||||
imageView.setImage(new Image(attachment.getThumbnailUrl(), 40, 40, true, true, true));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
|
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
|
||||||
|
|
||||||
TwoLineListItem content = new TwoLineListItem();
|
TwoLineListItem content = new TwoLineListItem();
|
||||||
HBox.setHgrow(content, Priority.ALWAYS);
|
HBox.setHgrow(content, Priority.ALWAYS);
|
||||||
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug());
|
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug());
|
||||||
content.setTitle(mod != null ? mod.getDisplayName() : getSkinnable().addon.getName());
|
content.setTitle(mod != null ? mod.getDisplayName() : getSkinnable().addon.getTitle());
|
||||||
content.setSubtitle(getSkinnable().addon.getSummary());
|
content.setSubtitle(getSkinnable().addon.getDescription());
|
||||||
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
|
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
|
||||||
.map(category -> i18n("curse.category." + category.getCategoryId()))
|
.map(category -> getSkinnable().page.getLocalizedCategory(category))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
descriptionPane.getChildren().add(content);
|
descriptionPane.getChildren().add(content);
|
||||||
|
|
||||||
@ -217,8 +222,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JFXHyperlink openUrlButton = new JFXHyperlink(i18n("mods.curseforge"));
|
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
|
||||||
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getWebsiteUrl()));
|
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getPageUrl()));
|
||||||
descriptionPane.getChildren().add(openUrlButton);
|
descriptionPane.getChildren().add(openUrlButton);
|
||||||
|
|
||||||
|
|
||||||
@ -234,10 +239,10 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
}
|
}
|
||||||
}, getSkinnable().failedProperty()));
|
}, getSkinnable().failedProperty()));
|
||||||
|
|
||||||
JFXListView<CurseAddon.LatestFile> listView = new JFXListView<>();
|
JFXListView<DownloadManager.Version> listView = new JFXListView<>();
|
||||||
spinnerPane.setContent(listView);
|
spinnerPane.setContent(listView);
|
||||||
Bindings.bindContent(listView.getItems(), getSkinnable().items);
|
Bindings.bindContent(listView.getItems(), getSkinnable().items);
|
||||||
listView.setCellFactory(x -> new FloatListCell<CurseAddon.LatestFile>(listView) {
|
listView.setCellFactory(x -> new FloatListCell<DownloadManager.Version>(listView) {
|
||||||
TwoLineListItem content = new TwoLineListItem();
|
TwoLineListItem content = new TwoLineListItem();
|
||||||
StackPane graphicPane = new StackPane();
|
StackPane graphicPane = new StackPane();
|
||||||
JFXButton saveAsButton = new JFXButton();
|
JFXButton saveAsButton = new JFXButton();
|
||||||
@ -255,23 +260,23 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateControl(CurseAddon.LatestFile dataItem, boolean empty) {
|
protected void updateControl(DownloadManager.Version dataItem, boolean empty) {
|
||||||
if (empty) return;
|
if (empty) return;
|
||||||
content.setTitle(dataItem.getDisplayName());
|
content.setTitle(dataItem.getName());
|
||||||
content.setSubtitle(FORMATTER.format(dataItem.getParsedFileDate()));
|
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
|
||||||
content.getTags().setAll(dataItem.getGameVersion());
|
content.getTags().setAll(dataItem.getGameVersions());
|
||||||
saveAsButton.setOnMouseClicked(e -> getSkinnable().saveAs(dataItem));
|
saveAsButton.setOnMouseClicked(e -> getSkinnable().saveAs(dataItem));
|
||||||
|
|
||||||
switch (dataItem.getReleaseType()) {
|
switch (dataItem.getVersionType()) {
|
||||||
case 1: // release
|
case Release:
|
||||||
graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24));
|
graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||||
content.getTags().add(i18n("version.game.release"));
|
content.getTags().add(i18n("version.game.release"));
|
||||||
break;
|
break;
|
||||||
case 2: // beta
|
case Beta:
|
||||||
graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||||
content.getTags().add(i18n("version.game.snapshot"));
|
content.getTags().add(i18n("version.game.snapshot"));
|
||||||
break;
|
break;
|
||||||
case 3: // alpha
|
case Alpha:
|
||||||
graphicPane.getChildren().setAll(SVG.alphaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
graphicPane.getChildren().setAll(SVG.alphaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||||
content.getTags().add(i18n("version.game.snapshot"));
|
content.getTags().add(i18n("version.game.snapshot"));
|
||||||
break;
|
break;
|
||||||
@ -282,7 +287,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
listView.setOnMouseClicked(e -> {
|
listView.setOnMouseClicked(e -> {
|
||||||
if (listView.getSelectionModel().getSelectedIndex() < 0)
|
if (listView.getSelectionModel().getSelectedIndex() < 0)
|
||||||
return;
|
return;
|
||||||
CurseAddon.LatestFile selectedItem = listView.getSelectionModel().getSelectedItem();
|
DownloadManager.Version selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||||
getSkinnable().download(selectedItem);
|
getSkinnable().download(selectedItem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -293,19 +298,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
|
|
||||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
|
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
|
||||||
|
|
||||||
public interface Project {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ProjectVersion {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DownloadSource {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DownloadCallback {
|
public interface DownloadCallback {
|
||||||
void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file);
|
void download(Profile profile, @Nullable String version, DownloadManager.Version file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,21 +17,29 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||||
|
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||||
|
import org.jackhuang.hmcl.mod.modrinth.Modrinth;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public class ModDownloadListPage extends DownloadListPage {
|
public class ModDownloadListPage extends DownloadListPage {
|
||||||
public ModDownloadListPage(int section, DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
public ModDownloadListPage(int section, DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||||
super(section, callback, versionSelection);
|
super(section, callback, versionSelection);
|
||||||
|
|
||||||
supportChinese.set(true);
|
supportChinese.set(true);
|
||||||
|
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
|
||||||
|
downloadSource.set("mods.curseforge");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<CurseAddon> searchImpl(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
|
protected Stream<DownloadManager.Mod> searchImpl(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
|
||||||
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
|
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
|
||||||
List<ModTranslations.Mod> mods = ModTranslations.searchMod(searchFilter);
|
List<ModTranslations.Mod> mods = ModTranslations.searchMod(searchFilter);
|
||||||
List<String> searchFilters = new ArrayList<>();
|
List<String> searchFilters = new ArrayList<>();
|
||||||
@ -47,9 +55,35 @@ public class ModDownloadListPage extends DownloadListPage {
|
|||||||
count++;
|
count++;
|
||||||
if (count >= 3) break;
|
if (count >= 3) break;
|
||||||
}
|
}
|
||||||
return super.searchImpl(gameVersion, category, section, pageOffset, String.join(" ", searchFilters), sort);
|
return search(gameVersion, category, section, pageOffset, String.join(" ", searchFilters), sort);
|
||||||
} else {
|
} else {
|
||||||
return super.searchImpl(gameVersion, category, section, pageOffset, searchFilter, sort);
|
return search(gameVersion, category, section, pageOffset, searchFilter, sort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<DownloadManager.Mod> search(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
|
||||||
|
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||||
|
return Modrinth.searchPaginated(gameVersion, pageOffset, searchFilter).stream().map(Modrinth.ModResult::toMod);
|
||||||
|
} else {
|
||||||
|
return CurseModManager.searchPaginated(gameVersion, category, section, pageOffset, searchFilter, sort).stream().map(CurseAddon::toMod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getLocalizedCategory(String category) {
|
||||||
|
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||||
|
return i18n("modrinth.category." + category);
|
||||||
|
} else {
|
||||||
|
return i18n("curse.category." + category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getLocalizedOfficialPage() {
|
||||||
|
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||||
|
return i18n("mods.modrinth");
|
||||||
|
} else {
|
||||||
|
return i18n("mods.curseforge");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import javafx.scene.control.Control;
|
|||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
@ -199,19 +199,19 @@ public class VersionPage extends Control implements DecoratorPage, DownloadPage.
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file) {
|
public void download(Profile profile, @Nullable String version, DownloadManager.Version file) {
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
throw new InternalError();
|
throw new InternalError();
|
||||||
}
|
}
|
||||||
|
|
||||||
Path dest = profile.getRepository().getRunDirectory(version).toPath().resolve("mods").resolve(file.getFileName());
|
Path dest = profile.getRepository().getRunDirectory(version).toPath().resolve("mods").resolve(file.getFile().getFilename());
|
||||||
|
|
||||||
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
|
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
|
||||||
});
|
});
|
||||||
|
|
||||||
TaskExecutor executor = Task.composeAsync(() -> {
|
TaskExecutor executor = Task.composeAsync(() -> {
|
||||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getDownloadUrl()), dest.toFile());
|
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
|
||||||
task.setName(file.getDisplayName());
|
task.setName(file.getName());
|
||||||
return task;
|
return task;
|
||||||
}).executor(false);
|
}).executor(false);
|
||||||
|
|
||||||
|
@ -18,16 +18,14 @@
|
|||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||||
import org.jackhuang.hmcl.game.GameDirectoryType;
|
import org.jackhuang.hmcl.game.GameDirectoryType;
|
||||||
import org.jackhuang.hmcl.game.GameRepository;
|
import org.jackhuang.hmcl.game.GameRepository;
|
||||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
@ -87,15 +85,15 @@ public final class Versions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void downloadModpackImpl(Profile profile, String version, CurseAddon.LatestFile file) {
|
public static void downloadModpackImpl(Profile profile, String version, DownloadManager.Version file) {
|
||||||
Path modpack;
|
Path modpack;
|
||||||
URL downloadURL;
|
URL downloadURL;
|
||||||
try {
|
try {
|
||||||
modpack = Files.createTempFile("modpack", ".zip");
|
modpack = Files.createTempFile("modpack", ".zip");
|
||||||
downloadURL = new URL(file.getDownloadUrl());
|
downloadURL = new URL(file.getFile().getUrl());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Controllers.dialog(
|
Controllers.dialog(
|
||||||
i18n("install.failed.downloading.detail", file.getDownloadUrl()) + "\n" + StringUtils.getStackTrace(e),
|
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||||
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
|
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -106,7 +104,7 @@ public final class Versions {
|
|||||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack.toFile()));
|
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack.toFile()));
|
||||||
} else {
|
} else {
|
||||||
Controllers.dialog(
|
Controllers.dialog(
|
||||||
i18n("install.failed.downloading.detail", file.getDownloadUrl()) + "\n" + StringUtils.getStackTrace(e),
|
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||||
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
|
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
|
||||||
}
|
}
|
||||||
}).executor(true),
|
}).executor(true),
|
||||||
|
@ -499,6 +499,20 @@ modpack.wizard.step.initialization.save=Export to...
|
|||||||
modpack.wizard.step.initialization.warning=Before creating a modpack, you should ensure that the game can launch successfully,\nand that your Minecraft is a release version.\nDo NOT add mods which cannot be redistributed.
|
modpack.wizard.step.initialization.warning=Before creating a modpack, you should ensure that the game can launch successfully,\nand that your Minecraft is a release version.\nDo NOT add mods which cannot be redistributed.
|
||||||
modpack.wizard.step.initialization.server=Click here for more information about server auto-update modpack
|
modpack.wizard.step.initialization.server=Click here for more information about server auto-update modpack
|
||||||
|
|
||||||
|
modrinth.category.adventure=Adventure
|
||||||
|
modrinth.category.cursed=Cursed
|
||||||
|
modrinth.category.decoration=Decoration
|
||||||
|
modrinth.category.equipment=Equipment
|
||||||
|
modrinth.category.fabric=Fabric
|
||||||
|
modrinth.category.food=Food
|
||||||
|
modrinth.category.library=Library
|
||||||
|
modrinth.category.magic=Magic
|
||||||
|
modrinth.category.misc=Misc
|
||||||
|
modrinth.category.storage=Storage
|
||||||
|
modrinth.category.technology=Technology
|
||||||
|
modrinth.category.utility=Utility
|
||||||
|
modrinth.category.worldgen=Worldgen
|
||||||
|
|
||||||
mods=Mods
|
mods=Mods
|
||||||
mods.add=Install mods
|
mods.add=Install mods
|
||||||
mods.add.failed=Failed to install mods %s.
|
mods.add.failed=Failed to install mods %s.
|
||||||
@ -515,6 +529,7 @@ mods.mcbbs=MCBBS
|
|||||||
mods.mcmod=MCMOD
|
mods.mcmod=MCMOD
|
||||||
mods.mcmod.page=MCMOD
|
mods.mcmod.page=MCMOD
|
||||||
mods.mcmod.search=Search in MCMOD
|
mods.mcmod.search=Search in MCMOD
|
||||||
|
mods.modrinth=Modrinth
|
||||||
mods.name=Name
|
mods.name=Name
|
||||||
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
|
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
|
||||||
mods.url=Official Page
|
mods.url=Official Page
|
||||||
|
@ -506,6 +506,20 @@ modpack.wizard.step.initialization.save=選擇要匯出到的遊戲整合包位
|
|||||||
modpack.wizard.step.initialization.warning=在製作整合包前,請您確認您選擇的版本可以正常啟動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應將不允許非官方途徑傳播的 Mod 模組、材質包等納入整合包。\n整合包會儲存您目前的下載來源設定
|
modpack.wizard.step.initialization.warning=在製作整合包前,請您確認您選擇的版本可以正常啟動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應將不允許非官方途徑傳播的 Mod 模組、材質包等納入整合包。\n整合包會儲存您目前的下載來源設定
|
||||||
modpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新整合包的製作教學
|
modpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新整合包的製作教學
|
||||||
|
|
||||||
|
modrinth.category.adventure=冒險
|
||||||
|
modrinth.category.cursed=Cursed
|
||||||
|
modrinth.category.decoration=裝飾
|
||||||
|
modrinth.category.equipment=裝備
|
||||||
|
modrinth.category.fabric=Fabric
|
||||||
|
modrinth.category.food=食物
|
||||||
|
modrinth.category.library=支持庫
|
||||||
|
modrinth.category.magic=魔法
|
||||||
|
modrinth.category.misc=其他
|
||||||
|
modrinth.category.storage=儲存
|
||||||
|
modrinth.category.technology=科技
|
||||||
|
modrinth.category.utility=實用
|
||||||
|
modrinth.category.worldgen=世界生成
|
||||||
|
|
||||||
mods=模組
|
mods=模組
|
||||||
mods.add=新增模組
|
mods.add=新增模組
|
||||||
mods.add.failed=新增模組 %s 失敗。
|
mods.add.failed=新增模組 %s 失敗。
|
||||||
@ -522,6 +536,7 @@ mods.mcbbs=MCBBS
|
|||||||
mods.mcmod=MC 百科
|
mods.mcmod=MC 百科
|
||||||
mods.mcmod.page=MC 百科頁面
|
mods.mcmod.page=MC 百科頁面
|
||||||
mods.mcmod.search=MC 百科蒐索
|
mods.mcmod.search=MC 百科蒐索
|
||||||
|
mods.modrinth=Modrinth
|
||||||
mods.name=名稱
|
mods.name=名稱
|
||||||
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
|
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
|
||||||
mods.url=官方頁面
|
mods.url=官方頁面
|
||||||
|
@ -499,6 +499,20 @@ modpack.wizard.step.initialization.save=选择要导出到的游戏整合包位
|
|||||||
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的 Minecraft 是正式版而非快照版,\n而且不应当将不允许非官方途径传播的 Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
|
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的 Minecraft 是正式版而非快照版,\n而且不应当将不允许非官方途径传播的 Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
|
||||||
modpack.wizard.step.initialization.server=点击此处查看有关服务器自动更新整合包的制作教程
|
modpack.wizard.step.initialization.server=点击此处查看有关服务器自动更新整合包的制作教程
|
||||||
|
|
||||||
|
modrinth.category.adventure=冒险
|
||||||
|
modrinth.category.cursed=Cursed
|
||||||
|
modrinth.category.decoration=装饰
|
||||||
|
modrinth.category.equipment=装备
|
||||||
|
modrinth.category.fabric=Fabric
|
||||||
|
modrinth.category.food=食物
|
||||||
|
modrinth.category.library=支持库
|
||||||
|
modrinth.category.magic=魔法
|
||||||
|
modrinth.category.misc=其他
|
||||||
|
modrinth.category.storage=存储
|
||||||
|
modrinth.category.technology=科技
|
||||||
|
modrinth.category.utility=实用
|
||||||
|
modrinth.category.worldgen=世界生成
|
||||||
|
|
||||||
mods=模组
|
mods=模组
|
||||||
mods.add=添加模组
|
mods.add=添加模组
|
||||||
mods.add.failed=添加模组 %s 失败。
|
mods.add.failed=添加模组 %s 失败。
|
||||||
@ -515,6 +529,7 @@ mods.mcbbs=MCBBS
|
|||||||
mods.mcmod=MC 百科
|
mods.mcmod=MC 百科
|
||||||
mods.mcmod.page=MC 百科页面
|
mods.mcmod.page=MC 百科页面
|
||||||
mods.mcmod.search=MC 百科搜索
|
mods.mcmod.search=MC 百科搜索
|
||||||
|
mods.modrinth=Modrinth
|
||||||
mods.name=名称
|
mods.name=名称
|
||||||
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
|
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
|
||||||
mods.url=官方页面
|
mods.url=官方页面
|
||||||
|
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 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;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public final class DownloadManager {
|
||||||
|
private DownloadManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IMod {
|
||||||
|
Stream<Version> loadVersions() throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Mod {
|
||||||
|
private final String slug;
|
||||||
|
private final String author;
|
||||||
|
private final String title;
|
||||||
|
private final String description;
|
||||||
|
private final List<String> categories;
|
||||||
|
private final String pageUrl;
|
||||||
|
private final String iconUrl;
|
||||||
|
private final IMod data;
|
||||||
|
|
||||||
|
public Mod(String slug, String author, String title, String description, List<String> categories, String pageUrl, String iconUrl, IMod data) {
|
||||||
|
this.slug = slug;
|
||||||
|
this.author = author;
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
this.categories = categories;
|
||||||
|
this.pageUrl = pageUrl;
|
||||||
|
this.iconUrl = iconUrl;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSlug() {
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCategories() {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPageUrl() {
|
||||||
|
return pageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIconUrl() {
|
||||||
|
return iconUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMod getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum VersionType {
|
||||||
|
Release,
|
||||||
|
Beta,
|
||||||
|
Alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Version {
|
||||||
|
private final Object self;
|
||||||
|
private final String name;
|
||||||
|
private final String version;
|
||||||
|
private final String changelog;
|
||||||
|
private final Instant datePublished;
|
||||||
|
private final VersionType versionType;
|
||||||
|
private final File file;
|
||||||
|
private final List<String> dependencies;
|
||||||
|
private final List<String> gameVersions;
|
||||||
|
private final List<String> loaders;
|
||||||
|
|
||||||
|
public Version(Object self, String name, String version, String changelog, Instant datePublished, VersionType versionType, File file, List<String> dependencies, List<String> gameVersions, List<String> loaders) {
|
||||||
|
this.self = self;
|
||||||
|
this.name = name;
|
||||||
|
this.version = version;
|
||||||
|
this.changelog = changelog;
|
||||||
|
this.datePublished = datePublished;
|
||||||
|
this.versionType = versionType;
|
||||||
|
this.file = file;
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
this.gameVersions = gameVersions;
|
||||||
|
this.loaders = loaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getSelf() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChangelog() {
|
||||||
|
return changelog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getDatePublished() {
|
||||||
|
return datePublished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VersionType getVersionType() {
|
||||||
|
return versionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getDependencies() {
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getGameVersions() {
|
||||||
|
return gameVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getLoaders() {
|
||||||
|
return loaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class File {
|
||||||
|
private final Map<String, String> hashes;
|
||||||
|
private final String url;
|
||||||
|
private final String filename;
|
||||||
|
|
||||||
|
public File(Map<String, String> hashes, String url, String filename) {
|
||||||
|
this.hashes = hashes;
|
||||||
|
this.url = url;
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getHashes() {
|
||||||
|
return hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String[] DEFAULT_GAME_VERSIONS = new String[]{
|
||||||
|
"1.17.1", "1.17",
|
||||||
|
"1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.16",
|
||||||
|
"1.15.2", "1.15.1", "1.15",
|
||||||
|
"1.14.4", "1.14.3", "1.14.2", "1.14.1", "1.14",
|
||||||
|
"1.13.2", "1.13.1", "1.13",
|
||||||
|
"1.12.2", "1.12.1", "1.12",
|
||||||
|
"1.11.2", "1.11.1", "1.11",
|
||||||
|
"1.10.2", "1.10.1", "1.10",
|
||||||
|
"1.9.4", "1.9.3", "1.9.2", "1.9.1", "1.9",
|
||||||
|
"1.8.9", "1.8.8", "1.8.7", "1.8.6", "1.8.5", "1.8.4", "1.8.3", "1.8.2", "1.8.1", "1.8",
|
||||||
|
"1.7.10", "1.7.9", "1.7.8", "1.7.7", "1.7.6", "1.7.5", "1.7.4", "1.7.3", "1.7.2",
|
||||||
|
"1.6.4", "1.6.2", "1.6.1",
|
||||||
|
"1.5.2", "1.5.1",
|
||||||
|
"1.4.7", "1.4.6", "1.4.5", "1.4.4", "1.4.2",
|
||||||
|
"1.3.2", "1.3.1",
|
||||||
|
"1.2.5", "1.2.4", "1.2.3", "1.2.2", "1.2.1",
|
||||||
|
"1.1",
|
||||||
|
"1.0"
|
||||||
|
};
|
||||||
|
}
|
@ -1,12 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 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.curse;
|
package org.jackhuang.hmcl.mod.curse;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
import org.jackhuang.hmcl.util.Immutable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
public class CurseAddon {
|
public class CurseAddon implements DownloadManager.IMod {
|
||||||
private final int id;
|
private final int id;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<Author> authors;
|
private final List<Author> authors;
|
||||||
@ -137,6 +159,32 @@ public class CurseAddon {
|
|||||||
return isExperimental;
|
return isExperimental;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<DownloadManager.Version> loadVersions() throws IOException {
|
||||||
|
return CurseModManager.getFiles(this).stream()
|
||||||
|
.map(CurseAddon.LatestFile::toVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadManager.Mod toMod() {
|
||||||
|
String iconUrl = null;
|
||||||
|
for (CurseAddon.Attachment attachment : attachments) {
|
||||||
|
if (attachment.isDefault()) {
|
||||||
|
iconUrl = attachment.getThumbnailUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DownloadManager.Mod(
|
||||||
|
slug,
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
summary,
|
||||||
|
categories.stream().map(category -> Integer.toString(category.getCategoryId())).collect(Collectors.toList()),
|
||||||
|
websiteUrl,
|
||||||
|
iconUrl,
|
||||||
|
this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
public static class Author {
|
public static class Author {
|
||||||
private final String name;
|
private final String name;
|
||||||
@ -410,6 +458,37 @@ public class CurseAddon {
|
|||||||
}
|
}
|
||||||
return fileDataInstant;
|
return fileDataInstant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DownloadManager.Version toVersion() {
|
||||||
|
DownloadManager.VersionType versionType;
|
||||||
|
switch (getReleaseType()) {
|
||||||
|
case 1:
|
||||||
|
versionType = DownloadManager.VersionType.Release;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
versionType = DownloadManager.VersionType.Beta;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
versionType = DownloadManager.VersionType.Alpha;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
versionType = DownloadManager.VersionType.Release;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DownloadManager.Version(
|
||||||
|
this,
|
||||||
|
getDisplayName(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
getParsedFileDate(),
|
||||||
|
versionType,
|
||||||
|
new DownloadManager.File(Collections.emptyMap(), getDownloadUrl(), getFileName()),
|
||||||
|
Collections.emptyList(),
|
||||||
|
gameVersion,
|
||||||
|
Collections.emptyList()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 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.curse;
|
package org.jackhuang.hmcl.mod.curse;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
@ -0,0 +1,479 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 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.modrinth;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||||
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||||
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
|
|
||||||
|
public final class Modrinth {
|
||||||
|
private Modrinth() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ModResult> searchPaginated(String gameVersion, int pageOffset, String searchFilter) throws IOException {
|
||||||
|
Map<String, String> query = mapOf(
|
||||||
|
pair("query", searchFilter),
|
||||||
|
pair("offset", Integer.toString(pageOffset)),
|
||||||
|
pair("limit", "25")
|
||||||
|
);
|
||||||
|
if (StringUtils.isNotBlank(gameVersion)) {
|
||||||
|
query.put("version", "versions=" + gameVersion);
|
||||||
|
}
|
||||||
|
Response<ModResult> response = HttpRequest.GET(NetworkUtils.withQuery("https://api.modrinth.com/api/v1/mod", query))
|
||||||
|
.getJson(new TypeToken<Response<ModResult>>() {
|
||||||
|
}.getType());
|
||||||
|
return response.getHits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ModVersion> getFiles(ModResult mod) throws IOException {
|
||||||
|
String id = StringUtils.removePrefix(mod.getModId(), "local-");
|
||||||
|
List<ModVersion> versions = HttpRequest.GET("https://api.modrinth.com/api/v1/mod/" + id + "/version")
|
||||||
|
.getJson(new TypeToken<List<ModVersion>>() {
|
||||||
|
}.getType());
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getCategories() throws IOException {
|
||||||
|
List<String> categories = HttpRequest.GET("https://api.modrinth.com/api/v1/tag/category").getJson(new TypeToken<List<String>>() {
|
||||||
|
}.getType());
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Mod {
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
private final String slug;
|
||||||
|
|
||||||
|
private final String team;
|
||||||
|
|
||||||
|
private final String title;
|
||||||
|
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
private final Instant published;
|
||||||
|
|
||||||
|
private final Instant updated;
|
||||||
|
|
||||||
|
private final List<String> categories;
|
||||||
|
|
||||||
|
private final List<String> versions;
|
||||||
|
|
||||||
|
private final int downloads;
|
||||||
|
|
||||||
|
@SerializedName("icon_url")
|
||||||
|
private final String iconUrl;
|
||||||
|
|
||||||
|
public Mod(String id, String slug, String team, String title, String description, Instant published, Instant updated, List<String> categories, List<String> versions, int downloads, String iconUrl) {
|
||||||
|
this.id = id;
|
||||||
|
this.slug = slug;
|
||||||
|
this.team = team;
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
this.published = published;
|
||||||
|
this.updated = updated;
|
||||||
|
this.categories = categories;
|
||||||
|
this.versions = versions;
|
||||||
|
this.downloads = downloads;
|
||||||
|
this.iconUrl = iconUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSlug() {
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTeam() {
|
||||||
|
return team;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getPublished() {
|
||||||
|
return published;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getUpdated() {
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCategories() {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getVersions() {
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDownloads() {
|
||||||
|
return downloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIconUrl() {
|
||||||
|
return iconUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ModVersion {
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
@SerializedName("mod_id")
|
||||||
|
private final String modId;
|
||||||
|
|
||||||
|
@SerializedName("author_id")
|
||||||
|
private final String authorId;
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@SerializedName("version_number")
|
||||||
|
private final String versionNumber;
|
||||||
|
|
||||||
|
private final String changelog;
|
||||||
|
|
||||||
|
@SerializedName("date_published")
|
||||||
|
private final Instant datePublished;
|
||||||
|
|
||||||
|
private final int downloads;
|
||||||
|
|
||||||
|
@SerializedName("version_type")
|
||||||
|
private final String versionType;
|
||||||
|
|
||||||
|
private final List<ModVersionFile> files;
|
||||||
|
|
||||||
|
private final List<String> dependencies;
|
||||||
|
|
||||||
|
@SerializedName("game_versions")
|
||||||
|
private final List<String> gameVersions;
|
||||||
|
|
||||||
|
private final List<String> loaders;
|
||||||
|
|
||||||
|
public ModVersion(String id, String modId, String authorId, String name, String versionNumber, String changelog, Instant datePublished, int downloads, String versionType, List<ModVersionFile> files, List<String> dependencies, List<String> gameVersions, List<String> loaders) {
|
||||||
|
this.id = id;
|
||||||
|
this.modId = modId;
|
||||||
|
this.authorId = authorId;
|
||||||
|
this.name = name;
|
||||||
|
this.versionNumber = versionNumber;
|
||||||
|
this.changelog = changelog;
|
||||||
|
this.datePublished = datePublished;
|
||||||
|
this.downloads = downloads;
|
||||||
|
this.versionType = versionType;
|
||||||
|
this.files = files;
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
this.gameVersions = gameVersions;
|
||||||
|
this.loaders = loaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModId() {
|
||||||
|
return modId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorId() {
|
||||||
|
return authorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersionNumber() {
|
||||||
|
return versionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChangelog() {
|
||||||
|
return changelog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getDatePublished() {
|
||||||
|
return datePublished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDownloads() {
|
||||||
|
return downloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersionType() {
|
||||||
|
return versionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ModVersionFile> getFiles() {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getDependencies() {
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getGameVersions() {
|
||||||
|
return gameVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getLoaders() {
|
||||||
|
return loaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<DownloadManager.Version> toVersion() {
|
||||||
|
DownloadManager.VersionType type;
|
||||||
|
if ("release".equals(versionType)) {
|
||||||
|
type = DownloadManager.VersionType.Release;
|
||||||
|
} else if ("beta".equals(versionType)) {
|
||||||
|
type = DownloadManager.VersionType.Beta;
|
||||||
|
} else if ("alpha".equals(versionType)) {
|
||||||
|
type = DownloadManager.VersionType.Alpha;
|
||||||
|
} else {
|
||||||
|
type = DownloadManager.VersionType.Release;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.size() == 0) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(new DownloadManager.Version(
|
||||||
|
this,
|
||||||
|
name,
|
||||||
|
versionNumber,
|
||||||
|
changelog,
|
||||||
|
datePublished,
|
||||||
|
type,
|
||||||
|
files.get(0).toFile(),
|
||||||
|
dependencies,
|
||||||
|
gameVersions,
|
||||||
|
loaders
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ModVersionFile {
|
||||||
|
private final Map<String, String> hashes;
|
||||||
|
private final String url;
|
||||||
|
private final String filename;
|
||||||
|
|
||||||
|
public ModVersionFile(Map<String, String> hashes, String url, String filename) {
|
||||||
|
this.hashes = hashes;
|
||||||
|
this.url = url;
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getHashes() {
|
||||||
|
return hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadManager.File toFile() {
|
||||||
|
return new DownloadManager.File(hashes, url, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ModResult implements DownloadManager.IMod {
|
||||||
|
@SerializedName("mod_id")
|
||||||
|
private final String modId;
|
||||||
|
|
||||||
|
private final String slug;
|
||||||
|
|
||||||
|
private final String author;
|
||||||
|
|
||||||
|
private final String title;
|
||||||
|
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
private final List<String> categories;
|
||||||
|
|
||||||
|
private final List<String> versions;
|
||||||
|
|
||||||
|
private final int downloads;
|
||||||
|
|
||||||
|
@SerializedName("page_url")
|
||||||
|
private final String pageUrl;
|
||||||
|
|
||||||
|
@SerializedName("icon_url")
|
||||||
|
private final String iconUrl;
|
||||||
|
|
||||||
|
@SerializedName("author_url")
|
||||||
|
private final String authorUrl;
|
||||||
|
|
||||||
|
@SerializedName("date_created")
|
||||||
|
private final Instant dateCreated;
|
||||||
|
|
||||||
|
@SerializedName("date_modified")
|
||||||
|
private final Instant dateModified;
|
||||||
|
|
||||||
|
@SerializedName("latest_version")
|
||||||
|
private final String latestVersion;
|
||||||
|
|
||||||
|
public ModResult(String modId, String slug, String author, String title, String description, List<String> categories, List<String> versions, int downloads, String pageUrl, String iconUrl, String authorUrl, Instant dateCreated, Instant dateModified, String latestVersion) {
|
||||||
|
this.modId = modId;
|
||||||
|
this.slug = slug;
|
||||||
|
this.author = author;
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
this.categories = categories;
|
||||||
|
this.versions = versions;
|
||||||
|
this.downloads = downloads;
|
||||||
|
this.pageUrl = pageUrl;
|
||||||
|
this.iconUrl = iconUrl;
|
||||||
|
this.authorUrl = authorUrl;
|
||||||
|
this.dateCreated = dateCreated;
|
||||||
|
this.dateModified = dateModified;
|
||||||
|
this.latestVersion = latestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModId() {
|
||||||
|
return modId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSlug() {
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCategories() {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getVersions() {
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDownloads() {
|
||||||
|
return downloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPageUrl() {
|
||||||
|
return pageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIconUrl() {
|
||||||
|
return iconUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorUrl() {
|
||||||
|
return authorUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getDateCreated() {
|
||||||
|
return dateCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getDateModified() {
|
||||||
|
return dateModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLatestVersion() {
|
||||||
|
return latestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<DownloadManager.Version> loadVersions() throws IOException {
|
||||||
|
return Modrinth.getFiles(this).stream()
|
||||||
|
.map(ModVersion::toVersion)
|
||||||
|
.flatMap(Lang::toStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadManager.Mod toMod() {
|
||||||
|
return new DownloadManager.Mod(
|
||||||
|
slug,
|
||||||
|
author,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
categories,
|
||||||
|
pageUrl,
|
||||||
|
iconUrl,
|
||||||
|
this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response<T> {
|
||||||
|
private final int offset;
|
||||||
|
|
||||||
|
private final int limit;
|
||||||
|
|
||||||
|
@SerializedName("total_hits")
|
||||||
|
private final int totalHits;
|
||||||
|
|
||||||
|
private final List<T> hits;
|
||||||
|
|
||||||
|
public Response() {
|
||||||
|
this(0, 0, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response(int offset, int limit, List<T> hits) {
|
||||||
|
this.offset = offset;
|
||||||
|
this.limit = limit;
|
||||||
|
this.totalHits = hits.size();
|
||||||
|
this.hits = hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLimit() {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalHits() {
|
||||||
|
return totalHits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> getHits() {
|
||||||
|
return hits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ import java.util.*;
|
|||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.*;
|
import java.util.function.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -315,6 +316,10 @@ public final class Lang {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> Stream<T> toStream(Optional<T> optional) {
|
||||||
|
return optional.map(Stream::of).orElseGet(Stream::empty);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a useful function to prevent exceptions being eaten when using CompletableFuture.
|
* This is a useful function to prevent exceptions being eaten when using CompletableFuture.
|
||||||
* You can write:
|
* You can write:
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 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.gson;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
public final class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
|
||||||
|
public static final InstantTypeAdapter INSTANCE = new InstantTypeAdapter();
|
||||||
|
|
||||||
|
private InstantTypeAdapter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
if (!(json instanceof JsonPrimitive)) {
|
||||||
|
throw new JsonParseException("The instant should be a string value");
|
||||||
|
} else {
|
||||||
|
Instant instant = Instant.parse(json.getAsString());
|
||||||
|
if (typeOfT == Instant.class) {
|
||||||
|
return instant;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(this.getClass() + " cannot be deserialized to " + typeOfT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return new JsonPrimitive(DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.systemDefault()).format(src));
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ import com.google.gson.JsonSyntaxException;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -63,6 +64,7 @@ public final class JsonUtils {
|
|||||||
return new GsonBuilder()
|
return new GsonBuilder()
|
||||||
.enableComplexMapKeySerialization()
|
.enableComplexMapKeySerialization()
|
||||||
.setPrettyPrinting()
|
.setPrettyPrinting()
|
||||||
|
.registerTypeAdapter(Instant.class, InstantTypeAdapter.INSTANCE)
|
||||||
.registerTypeAdapter(Date.class, DateTypeAdapter.INSTANCE)
|
.registerTypeAdapter(Date.class, DateTypeAdapter.INSTANCE)
|
||||||
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
|
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
|
||||||
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
||||||
|
@ -87,8 +87,8 @@ public final class NetworkUtils {
|
|||||||
public static URLConnection createConnection(URL url) throws IOException {
|
public static URLConnection createConnection(URL url) throws IOException {
|
||||||
URLConnection connection = url.openConnection();
|
URLConnection connection = url.openConnection();
|
||||||
connection.setUseCaches(false);
|
connection.setUseCaches(false);
|
||||||
connection.setConnectTimeout(15000);
|
connection.setConnectTimeout(5000);
|
||||||
connection.setReadTimeout(15000);
|
connection.setReadTimeout(5000);
|
||||||
connection.setRequestProperty("Accept-Language", Locale.getDefault().toString());
|
connection.setRequestProperty("Accept-Language", Locale.getDefault().toString());
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
@ -143,8 +143,8 @@ public final class NetworkUtils {
|
|||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
conn.setUseCaches(false);
|
conn.setUseCaches(false);
|
||||||
conn.setConnectTimeout(15000);
|
conn.setConnectTimeout(5000);
|
||||||
conn.setReadTimeout(15000);
|
conn.setReadTimeout(5000);
|
||||||
conn.setInstanceFollowRedirects(false);
|
conn.setInstanceFollowRedirects(false);
|
||||||
Map<String, List<String>> properties = conn.getRequestProperties();
|
Map<String, List<String>> properties = conn.getRequestProperties();
|
||||||
String method = conn.getRequestMethod();
|
String method = conn.getRequestMethod();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user