11package i18nupdatemod .util ;
22
3+ import com .google .gson .Gson ;
4+ import com .google .gson .reflect .TypeToken ;
35import org .apache .commons .io .FileUtils ;
46import org .apache .commons .io .IOUtils ;
7+ import org .jetbrains .annotations .NotNull ;
58
69import java .io .IOException ;
10+ import java .io .InputStreamReader ;
11+ import java .lang .reflect .Type ;
12+ import java .net .HttpURLConnection ;
713import java .net .URI ;
814import java .net .URISyntaxException ;
15+ import java .net .URL ;
916import java .nio .charset .StandardCharsets ;
1017import 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
1324public 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