refactor(download): RemoteModRepository
This commit is contained in:
@@ -18,22 +18,53 @@
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class DownloadManager {
|
||||
private DownloadManager() {
|
||||
}
|
||||
public interface RemoteModRepository {
|
||||
|
||||
public interface IMod {
|
||||
Stream<Mod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, int sort)
|
||||
throws IOException;
|
||||
|
||||
Optional<Version> getRemoteVersionByLocalFile(Path file) throws IOException;
|
||||
|
||||
Stream<Category> getCategories() throws IOException;
|
||||
|
||||
interface IMod {
|
||||
List<Mod> loadDependencies() throws IOException;
|
||||
|
||||
Stream<Version> loadVersions() throws IOException;
|
||||
}
|
||||
|
||||
public static class Mod {
|
||||
class Category {
|
||||
private final Object self;
|
||||
private final String id;
|
||||
private final List<Category> subcategories;
|
||||
|
||||
public Category(Object self, String id, List<Category> subcategories) {
|
||||
this.self = self;
|
||||
this.id = id;
|
||||
this.subcategories = subcategories;
|
||||
}
|
||||
|
||||
public Object getSelf() {
|
||||
return self;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<Category> getSubcategories() {
|
||||
return subcategories;
|
||||
}
|
||||
}
|
||||
|
||||
class Mod {
|
||||
private final String slug;
|
||||
private final String author;
|
||||
private final String title;
|
||||
@@ -87,13 +118,13 @@ public final class DownloadManager {
|
||||
}
|
||||
}
|
||||
|
||||
public enum VersionType {
|
||||
enum VersionType {
|
||||
Release,
|
||||
Beta,
|
||||
Alpha
|
||||
}
|
||||
|
||||
public static class Version {
|
||||
class Version {
|
||||
private final Object self;
|
||||
private final String name;
|
||||
private final String version;
|
||||
@@ -159,7 +190,7 @@ public final class DownloadManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static class File {
|
||||
class File {
|
||||
private final Map<String, String> hashes;
|
||||
private final String url;
|
||||
private final String filename;
|
||||
@@ -183,7 +214,7 @@ public final class DownloadManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static final String[] DEFAULT_GAME_VERSIONS = new String[]{
|
||||
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",
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod.curse;
|
||||
|
||||
import org.jackhuang.hmcl.mod.DownloadManager;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -30,7 +30,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Immutable
|
||||
public class CurseAddon implements DownloadManager.IMod {
|
||||
public class CurseAddon implements RemoteModRepository.IMod {
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final List<Author> authors;
|
||||
@@ -39,6 +39,7 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
private final int gameId;
|
||||
private final String summary;
|
||||
private final int defaultFileId;
|
||||
private final LatestFile file;
|
||||
private final List<LatestFile> latestFiles;
|
||||
private final List<Category> categories;
|
||||
private final int status;
|
||||
@@ -53,7 +54,7 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
private final boolean isAvailable;
|
||||
private final boolean isExperimental;
|
||||
|
||||
public CurseAddon(int id, String name, List<Author> authors, List<Attachment> attachments, String websiteUrl, int gameId, String summary, int defaultFileId, List<LatestFile> latestFiles, List<Category> categories, int status, int primaryCategoryId, String slug, List<GameVersionLatestFile> gameVersionLatestFiles, boolean isFeatured, double popularityScore, int gamePopularityRank, String primaryLanguage, List<String> modLoaders, boolean isAvailable, boolean isExperimental) {
|
||||
public CurseAddon(int id, String name, List<Author> authors, List<Attachment> attachments, String websiteUrl, int gameId, String summary, int defaultFileId, LatestFile file, List<LatestFile> latestFiles, List<Category> categories, int status, int primaryCategoryId, String slug, List<GameVersionLatestFile> gameVersionLatestFiles, boolean isFeatured, double popularityScore, int gamePopularityRank, String primaryLanguage, List<String> modLoaders, boolean isAvailable, boolean isExperimental) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.authors = authors;
|
||||
@@ -62,6 +63,7 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
this.gameId = gameId;
|
||||
this.summary = summary;
|
||||
this.defaultFileId = defaultFileId;
|
||||
this.file = file;
|
||||
this.latestFiles = latestFiles;
|
||||
this.categories = categories;
|
||||
this.status = status;
|
||||
@@ -109,6 +111,10 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
return defaultFileId;
|
||||
}
|
||||
|
||||
public LatestFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public List<LatestFile> getLatestFiles() {
|
||||
return latestFiles;
|
||||
}
|
||||
@@ -162,26 +168,26 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DownloadManager.Mod> loadDependencies() throws IOException {
|
||||
public List<RemoteModRepository.Mod> loadDependencies() throws IOException {
|
||||
Set<Integer> dependencies = latestFiles.stream()
|
||||
.flatMap(latestFile -> latestFile.getDependencies().stream())
|
||||
.filter(dep -> dep.getType() == 3)
|
||||
.map(Dependency::getAddonId)
|
||||
.collect(Collectors.toSet());
|
||||
List<DownloadManager.Mod> mods = new ArrayList<>();
|
||||
List<RemoteModRepository.Mod> mods = new ArrayList<>();
|
||||
for (int dependencyId : dependencies) {
|
||||
mods.add(CurseModManager.getAddon(dependencyId).toMod());
|
||||
mods.add(CurseForgeRemoteModRepository.MODS.getAddon(dependencyId).toMod());
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<DownloadManager.Version> loadVersions() throws IOException {
|
||||
return CurseModManager.getFiles(this).stream()
|
||||
public Stream<RemoteModRepository.Version> loadVersions() throws IOException {
|
||||
return CurseForgeRemoteModRepository.MODS.getFiles(this).stream()
|
||||
.map(CurseAddon.LatestFile::toVersion);
|
||||
}
|
||||
|
||||
public DownloadManager.Mod toMod() {
|
||||
public RemoteModRepository.Mod toMod() {
|
||||
String iconUrl = null;
|
||||
for (CurseAddon.Attachment attachment : attachments) {
|
||||
if (attachment.isDefault()) {
|
||||
@@ -189,7 +195,7 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
}
|
||||
}
|
||||
|
||||
return new DownloadManager.Mod(
|
||||
return new RemoteModRepository.Mod(
|
||||
slug,
|
||||
"",
|
||||
name,
|
||||
@@ -475,31 +481,31 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
return fileDataInstant;
|
||||
}
|
||||
|
||||
public DownloadManager.Version toVersion() {
|
||||
DownloadManager.VersionType versionType;
|
||||
public RemoteModRepository.Version toVersion() {
|
||||
RemoteModRepository.VersionType versionType;
|
||||
switch (getReleaseType()) {
|
||||
case 1:
|
||||
versionType = DownloadManager.VersionType.Release;
|
||||
versionType = RemoteModRepository.VersionType.Release;
|
||||
break;
|
||||
case 2:
|
||||
versionType = DownloadManager.VersionType.Beta;
|
||||
versionType = RemoteModRepository.VersionType.Beta;
|
||||
break;
|
||||
case 3:
|
||||
versionType = DownloadManager.VersionType.Alpha;
|
||||
versionType = RemoteModRepository.VersionType.Alpha;
|
||||
break;
|
||||
default:
|
||||
versionType = DownloadManager.VersionType.Release;
|
||||
versionType = RemoteModRepository.VersionType.Release;
|
||||
break;
|
||||
}
|
||||
|
||||
return new DownloadManager.Version(
|
||||
return new RemoteModRepository.Version(
|
||||
this,
|
||||
getDisplayName(),
|
||||
null,
|
||||
null,
|
||||
getParsedFileDate(),
|
||||
versionType,
|
||||
new DownloadManager.File(Collections.emptyMap(), getDownloadUrl(), getFileName()),
|
||||
new RemoteModRepository.File(Collections.emptyMap(), getDownloadUrl(), getFileName()),
|
||||
Collections.emptyList(),
|
||||
gameVersion.stream().filter(ver -> ver.startsWith("1.") || ver.contains("w")).collect(Collectors.toList()),
|
||||
Collections.emptyList()
|
||||
|
||||
@@ -18,27 +18,43 @@
|
||||
package org.jackhuang.hmcl.mod.curse;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.MurmurHash;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class CurseModManager {
|
||||
private CurseModManager() {
|
||||
public final class CurseForgeRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
private static final String PREFIX = "https://addons-ecs.forgesvc.net/api/v2";
|
||||
|
||||
private final int section;
|
||||
|
||||
public CurseForgeRemoteModRepository(int section) {
|
||||
this.section = section;
|
||||
}
|
||||
|
||||
public static List<CurseAddon> searchPaginated(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws IOException {
|
||||
String response = NetworkUtils.doGet(new URL(NetworkUtils.withQuery("https://addons-ecs.forgesvc.net/api/v2/addon/search", mapOf(
|
||||
public List<CurseAddon> searchPaginated(String gameVersion, int category, int pageOffset, int pageSize, String searchFilter, int sort) throws IOException {
|
||||
String response = NetworkUtils.doGet(new URL(NetworkUtils.withQuery(PREFIX + "/addon/search", mapOf(
|
||||
pair("categoryId", Integer.toString(category)),
|
||||
pair("gameId", "432"),
|
||||
pair("gameVersion", gameVersion),
|
||||
pair("index", Integer.toString(pageOffset)),
|
||||
pair("pageSize", "50"),
|
||||
pair("pageSize", Integer.toString(pageSize)),
|
||||
pair("searchFilter", searchFilter),
|
||||
pair("sectionId", Integer.toString(section)),
|
||||
pair("sort", Integer.toString(sort))
|
||||
@@ -47,25 +63,63 @@ public final class CurseModManager {
|
||||
}.getType());
|
||||
}
|
||||
|
||||
public static CurseAddon getAddon(int id) throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL("https://addons-ecs.forgesvc.net/api/v2/addon/" + id));
|
||||
@Override
|
||||
public Stream<RemoteModRepository.Mod> search(String gameVersion, RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, int sort) throws IOException {
|
||||
int categoryId = 0;
|
||||
if (category != null) categoryId = ((Category) category.getSelf()).getId();
|
||||
return searchPaginated(gameVersion, categoryId, pageOffset, pageSize, searchFilter, sort).stream()
|
||||
.map(CurseAddon::toMod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RemoteModRepository.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(file)))) {
|
||||
int b;
|
||||
while ((b = reader.read()) != -1) {
|
||||
if (b != 0x9 && b != 0xa && b != 0xd && b != 0x20) {
|
||||
baos.write(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int hash = MurmurHash.hash32(baos.toByteArray(), baos.size(), 1);
|
||||
|
||||
FingerprintResponse response = HttpRequest.POST(PREFIX + "/fingerprint")
|
||||
.json(Collections.singletonList(hash))
|
||||
.getJson(FingerprintResponse.class);
|
||||
|
||||
if (response.getExactMatches() == null || response.getExactMatches().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(response.getExactMatches().get(0).getFile().toVersion());
|
||||
}
|
||||
|
||||
public CurseAddon getAddon(int id) throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/addon/" + id));
|
||||
return JsonUtils.fromNonNullJson(response, CurseAddon.class);
|
||||
}
|
||||
|
||||
public static List<CurseAddon.LatestFile> getFiles(CurseAddon addon) throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL("https://addons-ecs.forgesvc.net/api/v2/addon/" + addon.getId() + "/files"));
|
||||
public List<CurseAddon.LatestFile> getFiles(CurseAddon addon) throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/addon/" + addon.getId() + "/files"));
|
||||
return JsonUtils.fromNonNullJson(response, new TypeToken<List<CurseAddon.LatestFile>>() {
|
||||
}.getType());
|
||||
}
|
||||
|
||||
public static List<Category> getCategories(int section) throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL("https://addons-ecs.forgesvc.net/api/v2/category/section/" + section));
|
||||
public List<Category> getCategoriesImpl() throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/category/section/" + section));
|
||||
List<Category> categories = JsonUtils.fromNonNullJson(response, new TypeToken<List<Category>>() {
|
||||
}.getType());
|
||||
return reorganizeCategories(categories, section);
|
||||
}
|
||||
|
||||
private static List<Category> reorganizeCategories(List<Category> categories, int rootId) {
|
||||
@Override
|
||||
public Stream<RemoteModRepository.Category> getCategories() throws IOException {
|
||||
return getCategoriesImpl().stream().map(Category::toCategory);
|
||||
}
|
||||
|
||||
private List<Category> reorganizeCategories(List<Category> categories, int rootId) {
|
||||
List<Category> result = new ArrayList<>();
|
||||
|
||||
Map<Integer, Category> categoryMap = new HashMap<>();
|
||||
@@ -98,6 +152,11 @@ public final class CurseModManager {
|
||||
public static final int SECTION_UNKNOWN2 = 4979;
|
||||
public static final int SECTION_UNKNOWN3 = 4984;
|
||||
|
||||
public static final CurseForgeRemoteModRepository MODS = new CurseForgeRemoteModRepository(SECTION_MOD);
|
||||
public static final CurseForgeRemoteModRepository MODPACKS = new CurseForgeRemoteModRepository(SECTION_MODPACK);
|
||||
public static final CurseForgeRemoteModRepository RESOURCE_PACKS = new CurseForgeRemoteModRepository(SECTION_RESOURCE_PACK);
|
||||
public static final CurseForgeRemoteModRepository WORLDS = new CurseForgeRemoteModRepository(SECTION_WORLD);
|
||||
|
||||
public static class Category {
|
||||
private final int id;
|
||||
private final String name;
|
||||
@@ -155,5 +214,36 @@ public final class CurseModManager {
|
||||
public List<Category> getSubcategories() {
|
||||
return subcategories;
|
||||
}
|
||||
|
||||
public RemoteModRepository.Category toCategory() {
|
||||
return new RemoteModRepository.Category(
|
||||
this,
|
||||
Integer.toString(id),
|
||||
getSubcategories().stream().map(Category::toCategory).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
private static class FingerprintResponse {
|
||||
private final boolean isCacheBuilt;
|
||||
private final List<CurseAddon> exactMatches;
|
||||
private final List<Integer> exactFingerprints;
|
||||
|
||||
public FingerprintResponse(boolean isCacheBuilt, List<CurseAddon> exactMatches, List<Integer> exactFingerprints) {
|
||||
this.isCacheBuilt = isCacheBuilt;
|
||||
this.exactMatches = exactMatches;
|
||||
this.exactFingerprints = exactFingerprints;
|
||||
}
|
||||
|
||||
public boolean isCacheBuilt() {
|
||||
return isCacheBuilt;
|
||||
}
|
||||
|
||||
public List<CurseAddon> getExactMatches() {
|
||||
return exactMatches;
|
||||
}
|
||||
|
||||
public List<Integer> getExactFingerprints() {
|
||||
return exactFingerprints;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,51 +19,89 @@ 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.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.Hex;
|
||||
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 org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
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 final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
public static final ModrinthRemoteModRepository INSTANCE = new ModrinthRemoteModRepository();
|
||||
|
||||
private static final String PREFIX = "https://api.modrinth.com";
|
||||
|
||||
private ModrinthRemoteModRepository() {
|
||||
}
|
||||
|
||||
public static List<ModResult> searchPaginated(String gameVersion, int pageOffset, String searchFilter) throws IOException {
|
||||
public List<ModResult> searchPaginated(String gameVersion, int pageOffset, int pageSize, String searchFilter) throws IOException {
|
||||
Map<String, String> query = mapOf(
|
||||
pair("query", searchFilter),
|
||||
pair("offset", Integer.toString(pageOffset)),
|
||||
pair("limit", "50")
|
||||
pair("limit", Integer.toString(pageSize))
|
||||
);
|
||||
if (StringUtils.isNotBlank(gameVersion)) {
|
||||
query.put("version", "versions=" + gameVersion);
|
||||
}
|
||||
Response<ModResult> response = HttpRequest.GET(NetworkUtils.withQuery("https://api.modrinth.com/api/v1/mod", query))
|
||||
Response<ModResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/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;
|
||||
@Override
|
||||
public Stream<RemoteModRepository.Mod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, int sort) throws IOException {
|
||||
return searchPaginated(gameVersion, pageOffset, pageSize, searchFilter).stream()
|
||||
.map(ModResult::toMod);
|
||||
}
|
||||
|
||||
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>>() {
|
||||
@Override
|
||||
public Optional<RemoteModRepository.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
|
||||
String sha1 = Hex.encodeHex(DigestUtils.digest("SHA-1", file));
|
||||
|
||||
try {
|
||||
ModVersion mod = HttpRequest.GET(PREFIX + "/api/v1/version_file/" + sha1,
|
||||
pair("algorithm", "sha1"))
|
||||
.getJson(ModVersion.class);
|
||||
return mod.toVersion();
|
||||
} catch (ResponseCodeException e) {
|
||||
if (e.getResponseCode() == 404) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ModVersion> getFiles(ModResult mod) throws IOException {
|
||||
String id = StringUtils.removePrefix(mod.getModId(), "local-");
|
||||
return HttpRequest.GET("https://api.modrinth.com/api/v1/mod/" + id + "/version")
|
||||
.getJson(new TypeToken<List<ModVersion>>() {
|
||||
}.getType());
|
||||
}
|
||||
|
||||
public List<String> getCategoriesImpl() throws IOException {
|
||||
return HttpRequest.GET("https://api.modrinth.com/api/v1/tag/category").getJson(new TypeToken<List<String>>() {
|
||||
}.getType());
|
||||
return categories;
|
||||
}
|
||||
|
||||
public Stream<Category> getCategories() throws IOException {
|
||||
return getCategoriesImpl().stream()
|
||||
.map(name -> new Category(null, name, Collections.emptyList()));
|
||||
}
|
||||
|
||||
public static class Mod {
|
||||
@@ -250,23 +288,23 @@ public final class Modrinth {
|
||||
return loaders;
|
||||
}
|
||||
|
||||
public Optional<DownloadManager.Version> toVersion() {
|
||||
DownloadManager.VersionType type;
|
||||
public Optional<RemoteModRepository.Version> toVersion() {
|
||||
RemoteModRepository.VersionType type;
|
||||
if ("release".equals(versionType)) {
|
||||
type = DownloadManager.VersionType.Release;
|
||||
type = RemoteModRepository.VersionType.Release;
|
||||
} else if ("beta".equals(versionType)) {
|
||||
type = DownloadManager.VersionType.Beta;
|
||||
type = RemoteModRepository.VersionType.Beta;
|
||||
} else if ("alpha".equals(versionType)) {
|
||||
type = DownloadManager.VersionType.Alpha;
|
||||
type = RemoteModRepository.VersionType.Alpha;
|
||||
} else {
|
||||
type = DownloadManager.VersionType.Release;
|
||||
type = RemoteModRepository.VersionType.Release;
|
||||
}
|
||||
|
||||
if (files.size() == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(new DownloadManager.Version(
|
||||
return Optional.of(new RemoteModRepository.Version(
|
||||
this,
|
||||
name,
|
||||
versionNumber,
|
||||
@@ -304,12 +342,12 @@ public final class Modrinth {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public DownloadManager.File toFile() {
|
||||
return new DownloadManager.File(hashes, url, filename);
|
||||
public RemoteModRepository.File toFile() {
|
||||
return new RemoteModRepository.File(hashes, url, filename);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModResult implements DownloadManager.IMod {
|
||||
public static class ModResult implements RemoteModRepository.IMod {
|
||||
@SerializedName("mod_id")
|
||||
private final String modId;
|
||||
|
||||
@@ -419,19 +457,19 @@ public final class Modrinth {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DownloadManager.Mod> loadDependencies() throws IOException {
|
||||
public List<RemoteModRepository.Mod> loadDependencies() throws IOException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<DownloadManager.Version> loadVersions() throws IOException {
|
||||
return Modrinth.getFiles(this).stream()
|
||||
public Stream<RemoteModRepository.Version> loadVersions() throws IOException {
|
||||
return ModrinthRemoteModRepository.INSTANCE.getFiles(this).stream()
|
||||
.map(ModVersion::toVersion)
|
||||
.flatMap(Lang::toStream);
|
||||
}
|
||||
|
||||
public DownloadManager.Mod toMod() {
|
||||
return new DownloadManager.Mod(
|
||||
public RemoteModRepository.Mod toMod() {
|
||||
return new RemoteModRepository.Mod(
|
||||
slug,
|
||||
author,
|
||||
title,
|
||||
214
HMCLCore/src/main/java/org/jackhuang/hmcl/util/MurmurHash.java
Normal file
214
HMCLCore/src/main/java/org/jackhuang/hmcl/util/MurmurHash.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* murmur hash 2.0.
|
||||
*
|
||||
* The murmur hash is a relatively fast hash function from
|
||||
* http://murmurhash.googlepages.com/ for platforms with efficient
|
||||
* multiplication.
|
||||
*
|
||||
* This is a re-implementation of the original C code plus some
|
||||
* additional features.
|
||||
*
|
||||
* Public domain.
|
||||
*
|
||||
* @author Viliam Holub
|
||||
* @version 1.0.2
|
||||
*
|
||||
*/
|
||||
public class MurmurHash {
|
||||
|
||||
// all methods static; private constructor.
|
||||
private MurmurHash() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 32 bit hash from byte array of the given length and
|
||||
* seed.
|
||||
*
|
||||
* @param data byte array to hash
|
||||
* @param length length of the array to hash
|
||||
* @param seed initial seed value
|
||||
* @return 32 bit hash of the given array
|
||||
*/
|
||||
public static int hash32(final byte[] data, int length, int seed) {
|
||||
// 'm' and 'r' are mixing constants generated offline.
|
||||
// They're not really 'magic', they just happen to work well.
|
||||
final int m = 0x5bd1e995;
|
||||
final int r = 24;
|
||||
|
||||
// Initialize the hash to a random value
|
||||
int h = seed ^ length;
|
||||
int length4 = length / 4;
|
||||
|
||||
for (int i = 0; i < length4; i++) {
|
||||
final int i4 = i * 4;
|
||||
int k = (data[i4 + 0] & 0xff) + ((data[i4 + 1] & 0xff) << 8)
|
||||
+ ((data[i4 + 2] & 0xff) << 16) + ((data[i4 + 3] & 0xff) << 24);
|
||||
k *= m;
|
||||
k ^= k >>> r;
|
||||
k *= m;
|
||||
h *= m;
|
||||
h ^= k;
|
||||
}
|
||||
|
||||
// Handle the last few bytes of the input array
|
||||
switch (length % 4) {
|
||||
case 3:
|
||||
h ^= (data[(length & ~3) + 2] & 0xff) << 16;
|
||||
case 2:
|
||||
h ^= (data[(length & ~3) + 1] & 0xff) << 8;
|
||||
case 1:
|
||||
h ^= (data[length & ~3] & 0xff);
|
||||
h *= m;
|
||||
}
|
||||
|
||||
h ^= h >>> 13;
|
||||
h *= m;
|
||||
h ^= h >>> 15;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 32 bit hash from byte array with default seed value.
|
||||
*
|
||||
* @param data byte array to hash
|
||||
* @param length length of the array to hash
|
||||
* @return 32 bit hash of the given array
|
||||
*/
|
||||
public static int hash32(final byte[] data, int length) {
|
||||
return hash32(data, length, 0x9747b28c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 32 bit hash from a string.
|
||||
*
|
||||
* @param text string to hash
|
||||
* @return 32 bit hash of the given string
|
||||
*/
|
||||
public static int hash32(final String text) {
|
||||
final byte[] bytes = text.getBytes();
|
||||
return hash32(bytes, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 32 bit hash from a substring.
|
||||
*
|
||||
* @param text string to hash
|
||||
* @param from starting index
|
||||
* @param length length of the substring to hash
|
||||
* @return 32 bit hash of the given string
|
||||
*/
|
||||
public static int hash32(final String text, int from, int length) {
|
||||
return hash32(text.substring(from, from + length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 64 bit hash from byte array of the given length and seed.
|
||||
*
|
||||
* @param data byte array to hash
|
||||
* @param length length of the array to hash
|
||||
* @param seed initial seed value
|
||||
* @return 64 bit hash of the given array
|
||||
*/
|
||||
public static long hash64(final byte[] data, int length, int seed) {
|
||||
final long m = 0xc6a4a7935bd1e995L;
|
||||
final int r = 47;
|
||||
|
||||
long h = (seed & 0xffffffffl) ^ (length * m);
|
||||
|
||||
int length8 = length / 8;
|
||||
|
||||
for (int i = 0; i < length8; i++) {
|
||||
final int i8 = i * 8;
|
||||
long k = ((long) data[i8 + 0] & 0xff) + (((long) data[i8 + 1] & 0xff) << 8)
|
||||
+ (((long) data[i8 + 2] & 0xff) << 16) + (((long) data[i8 + 3] & 0xff) << 24)
|
||||
+ (((long) data[i8 + 4] & 0xff) << 32) + (((long) data[i8 + 5] & 0xff) << 40)
|
||||
+ (((long) data[i8 + 6] & 0xff) << 48) + (((long) data[i8 + 7] & 0xff) << 56);
|
||||
|
||||
k *= m;
|
||||
k ^= k >>> r;
|
||||
k *= m;
|
||||
|
||||
h ^= k;
|
||||
h *= m;
|
||||
}
|
||||
|
||||
switch (length % 8) {
|
||||
case 7:
|
||||
h ^= (long) (data[(length & ~7) + 6] & 0xff) << 48;
|
||||
case 6:
|
||||
h ^= (long) (data[(length & ~7) + 5] & 0xff) << 40;
|
||||
case 5:
|
||||
h ^= (long) (data[(length & ~7) + 4] & 0xff) << 32;
|
||||
case 4:
|
||||
h ^= (long) (data[(length & ~7) + 3] & 0xff) << 24;
|
||||
case 3:
|
||||
h ^= (long) (data[(length & ~7) + 2] & 0xff) << 16;
|
||||
case 2:
|
||||
h ^= (long) (data[(length & ~7) + 1] & 0xff) << 8;
|
||||
case 1:
|
||||
h ^= (long) (data[length & ~7] & 0xff);
|
||||
h *= m;
|
||||
}
|
||||
;
|
||||
|
||||
h ^= h >>> r;
|
||||
h *= m;
|
||||
h ^= h >>> r;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 64 bit hash from byte array with default seed value.
|
||||
*
|
||||
* @param data byte array to hash
|
||||
* @param length length of the array to hash
|
||||
* @return 64 bit hash of the given string
|
||||
*/
|
||||
public static long hash64(final byte[] data, int length) {
|
||||
return hash64(data, length, 0xe17a1465);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 64 bit hash from a string.
|
||||
*
|
||||
* @param text string to hash
|
||||
* @return 64 bit hash of the given string
|
||||
*/
|
||||
public static long hash64(final String text) {
|
||||
final byte[] bytes = text.getBytes();
|
||||
return hash64(bytes, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 64 bit hash from a substring.
|
||||
*
|
||||
* @param text string to hash
|
||||
* @param from starting index
|
||||
* @param length length of the substring to hash
|
||||
* @return 64 bit hash of the given array
|
||||
*/
|
||||
public static long hash64(final String text, int from, int length) {
|
||||
return hash64(text.substring(from, from + length));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user