Skip to content

Commit 4062de1

Browse files
authored
feat: Support download from mirrors (#41)
* feat: Support download from mirrors * 优化Github下载逻辑、添加个人镜像站 * 修改代码适应packer修改 * 更换了速度更快的镜像服务器 * 标准化变量名 * fix: fix wrong replace all * fix: 优化Github匹配规则 * Clean up comment in AssetUtil Removed commented out localhost mirror URL.
1 parent a1d52a0 commit 4062de1

2 files changed

Lines changed: 158 additions & 8 deletions

File tree

src/main/java/i18nupdatemod/core/I18nConfig.java

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
import java.io.InputStream;
1313
import java.io.InputStreamReader;
1414
import java.util.List;
15+
import java.util.Map;
1516
import java.util.stream.Collectors;
1617

18+
import static i18nupdatemod.util.AssetUtil.getFastestUrl;
19+
import static i18nupdatemod.util.AssetUtil.getGitIndex;
20+
1721
public class I18nConfig {
1822
/**
1923
* <a href="https://github.com/CFPAOrg/Minecraft-Mod-Language-Package">CFPAOrg/Minecraft-Mod-Language-Package</a>
@@ -58,17 +62,60 @@ public static GameAssetDetail getAssetDetail(String minecraftVersion, String loa
5862
GameMetaData convert = getGameMetaData(minecraftVersion);
5963
GameAssetDetail ret = new GameAssetDetail();
6064

61-
ret.downloads = convert.convertFrom.stream().map(it -> getAssetMetaData(it, loader)).map(it -> {
65+
String assetRoot = getFastestUrl();
66+
Log.debug("Using asset root: " + assetRoot);
67+
68+
if (assetRoot.equals("https://raw.githubusercontent.com/")) {
69+
ret.downloads = createDownloadDetailsFromGit(convert, loader);
70+
} else {
71+
ret.downloads = createDownloadDetails(convert, loader, assetRoot);
72+
}
73+
74+
ret.covertPackFormat = convert.packFormat;
75+
ret.covertFileName =
76+
String.format("Minecraft-Mod-Language-Modpack-Converted-%s.zip", minecraftVersion);
77+
return ret;
78+
}
79+
80+
private static List<GameAssetDetail.AssetDownloadDetail> createDownloadDetails(GameMetaData convert, String loader, String assetRoot) {
81+
return convert.convertFrom.stream().map(it -> getAssetMetaData(it, loader)).map(it -> {
6282
GameAssetDetail.AssetDownloadDetail adi = new GameAssetDetail.AssetDownloadDetail();
6383
adi.fileName = it.filename;
64-
adi.fileUrl = CFPA_ASSET_ROOT + it.filename;
65-
adi.md5Url = CFPA_ASSET_ROOT + it.md5Filename;
84+
adi.fileUrl = assetRoot + it.filename;
85+
adi.md5Url = assetRoot + it.md5Filename;
6686
adi.targetVersion = it.targetVersion;
6787
return adi;
6888
}).collect(Collectors.toList());
69-
ret.covertPackFormat = convert.packFormat;
70-
ret.covertFileName =
71-
String.format("Minecraft-Mod-Language-Modpack-Converted-%s.zip", minecraftVersion);
72-
return ret;
89+
}
90+
91+
private static List<GameAssetDetail.AssetDownloadDetail> createDownloadDetailsFromGit(GameMetaData convert, String loader) {
92+
try {
93+
Map<String, String> index = getGitIndex();
94+
String releaseTag;
95+
String version = convert.convertFrom.get(0);
96+
97+
if (loader.toLowerCase().contains("fabric")) {
98+
releaseTag = index.get(version + "-fabric");
99+
} else {
100+
releaseTag = index.get(version);
101+
}
102+
if (releaseTag == null) {
103+
Log.debug("Error getting index: " + version + "-" + loader);
104+
Log.debug(index.toString());
105+
throw new Exception();
106+
}
107+
String assetRoot = "https://github.com/CFPAOrg/Minecraft-Mod-Language-Package/releases/download/" + releaseTag + "/";
108+
109+
return convert.convertFrom.stream().map(it -> getAssetMetaData(it, loader)).map(it -> {
110+
GameAssetDetail.AssetDownloadDetail adi = new GameAssetDetail.AssetDownloadDetail();
111+
adi.fileName = it.filename;
112+
adi.fileUrl = assetRoot + it.filename;
113+
adi.md5Url = assetRoot + it.md5Filename;
114+
adi.targetVersion = it.targetVersion;
115+
return adi;
116+
}).collect(Collectors.toList());
117+
} catch (Exception ignore) {
118+
return createDownloadDetails(convert, loader, CFPA_ASSET_ROOT);
119+
}
73120
}
74121
}

src/main/java/i18nupdatemod/util/AssetUtil.java

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
package i18nupdatemod.util;
22

3+
import com.google.gson.Gson;
4+
import com.google.gson.reflect.TypeToken;
35
import org.apache.commons.io.FileUtils;
46
import org.apache.commons.io.IOUtils;
7+
import org.jetbrains.annotations.NotNull;
58

69
import java.io.IOException;
10+
import java.io.InputStreamReader;
11+
import java.lang.reflect.Type;
12+
import java.net.HttpURLConnection;
713
import java.net.URI;
814
import java.net.URISyntaxException;
15+
import java.net.URL;
916
import java.nio.charset.StandardCharsets;
1017
import java.nio.file.Path;
11-
import java.util.concurrent.TimeUnit;
18+
import java.util.ArrayList;
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.concurrent.*;
1223

1324
public class AssetUtil {
25+
private static final String CFPA_ASSET_ROOT = "http://downloader1.meitangdehulu.com:22943/";
26+
private static final List<String> MIRRORS;
27+
28+
static {
29+
// 镜像地址可以改成服务器下发
30+
MIRRORS = new ArrayList<>();
31+
MIRRORS.add("https://raw.githubusercontent.com/");
32+
// 此镜像源维护者:502y
33+
MIRRORS.add("http://8.137.167.65:64684/");
34+
}
35+
1436
public static void download(String url, Path localFile) throws IOException, URISyntaxException {
1537
Log.info("Downloading: %s -> %s", url, localFile);
1638
FileUtils.copyURLToFile(new URI(url).toURL(), localFile.toFile(),
@@ -21,4 +43,85 @@ public static void download(String url, Path localFile) throws IOException, URIS
2143
public static String getString(String url) throws IOException, URISyntaxException {
2244
return IOUtils.toString(new URI(url).toURL(), StandardCharsets.UTF_8);
2345
}
46+
47+
public static String getFastestUrl() {
48+
List<String> urls = new ArrayList<>(MIRRORS);
49+
urls.add(CFPA_ASSET_ROOT);
50+
51+
ExecutorService executor = Executors.newFixedThreadPool(Math.max(urls.size(), 10));
52+
try {
53+
List<CompletableFuture<String>> futures = new ArrayList<>();
54+
for (String url : urls) {
55+
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
56+
try {
57+
return testUrlConnection(url);
58+
} catch (IOException e) {
59+
return null; // 表示失败
60+
}
61+
}, executor);
62+
futures.add(future);
63+
}
64+
65+
// 阻塞等待最快完成且成功的任务
66+
String fastest = null;
67+
while (!futures.isEmpty()) {
68+
CompletableFuture<Object> first = CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]));
69+
fastest = (String) first.join();
70+
71+
// 移除已完成的 future
72+
futures.removeIf(CompletableFuture::isDone);
73+
74+
if (fastest != null) {
75+
// 成功,取消其他任务
76+
for (CompletableFuture<String> f : futures) {
77+
f.cancel(true);
78+
}
79+
Log.info("Using fastest url: %s", fastest);
80+
return fastest;
81+
}
82+
}
83+
84+
// 全部失败,返回默认 URL
85+
Log.info("All urls are unreachable, using CFPA_ASSET_ROOT");
86+
return CFPA_ASSET_ROOT;
87+
88+
} finally {
89+
executor.shutdownNow();
90+
}
91+
}
92+
93+
private static String testUrlConnection(String url) throws IOException {
94+
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
95+
conn.setRequestMethod("HEAD");
96+
conn.setConnectTimeout(3000);
97+
conn.setReadTimeout(5000);
98+
conn.connect();
99+
int code = conn.getResponseCode();
100+
if (code >= 200 && code < 300) {
101+
return url;
102+
}
103+
Log.debug("URL unreachable: %s, code: %d", url, code);
104+
throw new IOException("URL unreachable: " + url);
105+
}
106+
107+
@NotNull
108+
public static Map<String, String> getGitIndex() {
109+
try {
110+
URL index_url = new URL("https://raw.githubusercontent.com/CFPAOrg/Minecraft-Mod-Language-Package/refs/heads/index/version-index.json");
111+
HttpURLConnection httpConn = (HttpURLConnection) index_url.openConnection();
112+
httpConn.setRequestMethod("GET");
113+
httpConn.setConnectTimeout(5000);
114+
httpConn.setReadTimeout(5000);
115+
116+
try (InputStreamReader reader = new InputStreamReader(httpConn.getInputStream())) {
117+
Type mapType = new TypeToken<Map<String, String>>() {
118+
}.getType();
119+
return new Gson().fromJson(reader, mapType);
120+
} finally {
121+
httpConn.disconnect();
122+
}
123+
} catch (Exception ignore) {
124+
return new HashMap<>();
125+
}
126+
}
24127
}

0 commit comments

Comments
 (0)