Skip to content

Commit bc4c4a3

Browse files
committed
Download Utils! Because SL and MCMaven both need it
1 parent dbd4f3d commit bc4c4a3

File tree

5 files changed

+281
-1
lines changed

5 files changed

+281
-1
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Build Download Utils
2+
on:
3+
push:
4+
branches: [master]
5+
paths:
6+
- download-utils/**
7+
- '!.github/workflows/**'
8+
- '!settings.gradle'
9+
permissions:
10+
contents: read
11+
jobs:
12+
build:
13+
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@v0
14+
with:
15+
java: 21
16+
gradle_tasks: :download-utils:check :download-utils:publish
17+
project_path: download-utils
18+
artifact_name: download-utils
19+
secrets:
20+
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
21+
PROMOTE_ARTIFACT_WEBHOOK: ${{ secrets.PROMOTE_ARTIFACT_WEBHOOK }}
22+
PROMOTE_ARTIFACT_USERNAME: ${{ secrets.PROMOTE_ARTIFACT_USERNAME }}
23+
PROMOTE_ARTIFACT_PASSWORD: ${{ secrets.PROMOTE_ARTIFACT_PASSWORD }}
24+
MAVEN_USER: ${{ secrets.MAVEN_USER }}
25+
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
26+
GRADLE_CACHE_KEY: ${{ secrets.GRADLE_CACHE_KEY }}

.gitversion

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ tag = "log"
1313
[data]
1414
path = "json-data-utils"
1515
tag = "json-data"
16+
17+
[download]
18+
path = "download-utils"
19+
tag = "download"

download-utils/build.gradle

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
plugins {
2+
id 'java-library'
3+
id 'idea'
4+
id 'maven-publish'
5+
id 'net.minecraftforge.licenser' version '1.1.1'
6+
id 'net.minecraftforge.gradleutils' version '2.4.13'
7+
}
8+
9+
apply from: rootProject.file('build_shared.gradle')
10+
11+
dependencies {
12+
implementation project(':log-utils')
13+
14+
implementation libs.gson
15+
16+
// Static Analysis
17+
compileOnly libs.nulls
18+
}
19+
20+
java {
21+
toolchain.languageVersion = JavaLanguageVersion.of(8)
22+
withSourcesJar()
23+
}
24+
25+
jar {
26+
manifest {
27+
attributes('Automatic-Module-Name': 'net.minecraftforge.utils.download')
28+
attributes([
29+
'Specification-Title': 'Download Utils',
30+
'Specification-Vendor': 'Forge Development LLC',
31+
'Specification-Version': gitversion.version.info.tag,
32+
'Implementation-Title': 'Download Utils',
33+
'Implementation-Vendor': 'Forge Development LLC',
34+
'Implementation-Version': project.version
35+
] as LinkedHashMap, 'net/minecraftforge/util/download/')
36+
}
37+
}
38+
39+
publishing {
40+
publications.register('mavenJava', MavenPublication).configure {
41+
artifactId = project.name
42+
from components.java
43+
44+
pom { pom ->
45+
name = 'Download Utils'
46+
description = 'Utilities for simple download tasks'
47+
48+
gradleutils.pom.gitHubDetails = pom
49+
50+
license gradleutils.pom.licenses.LGPLv2_1
51+
52+
developers {
53+
developer gradleutils.pom.Developers.LexManos
54+
developer gradleutils.pom.Developers.Jonathing
55+
}
56+
}
57+
}
58+
repositories {
59+
maven gradleutils.publishingForgeMaven
60+
}
61+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright (c) Forge Development LLC
3+
* SPDX-License-Identifier: LGPL-2.1-only
4+
*/
5+
package net.minecraftforge.util.download;
6+
7+
import com.google.gson.Gson;
8+
import com.google.gson.GsonBuilder;
9+
import com.google.gson.TypeAdapter;
10+
import com.google.gson.stream.JsonReader;
11+
import com.google.gson.stream.JsonToken;
12+
import com.google.gson.stream.JsonWriter;
13+
import net.minecraftforge.util.logging.SimpleLogger;
14+
import org.jetbrains.annotations.Nullable;
15+
16+
import javax.net.ssl.SSLHandshakeException;
17+
import java.io.ByteArrayOutputStream;
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.net.HttpURLConnection;
22+
import java.net.MalformedURLException;
23+
import java.net.URI;
24+
import java.net.URISyntaxException;
25+
import java.net.URL;
26+
import java.net.URLConnection;
27+
import java.nio.charset.StandardCharsets;
28+
import java.nio.file.Files;
29+
import java.nio.file.StandardCopyOption;
30+
31+
public final class DownloadUtils {
32+
private static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
33+
@Override
34+
public String read(JsonReader in) throws IOException {
35+
JsonToken peek = in.peek();
36+
if (peek == JsonToken.NULL) {
37+
in.nextNull();
38+
return null;
39+
}
40+
/* coerce booleans to strings for backwards compatibility */
41+
if (peek == JsonToken.BOOLEAN) {
42+
return Boolean.toString(in.nextBoolean());
43+
}
44+
return in.nextString();
45+
}
46+
@Override
47+
public void write(JsonWriter out, String value) throws IOException {
48+
out.value(value);
49+
}
50+
};
51+
52+
// A GSO parser that prints good looking output, and treats empty strings as nulls
53+
public static final Gson GSON = new GsonBuilder()
54+
.setLenient()
55+
.setPrettyPrinting()
56+
.registerTypeAdapter(String.class, new TypeAdapter<String>() {
57+
@Override
58+
public void write(final JsonWriter out, final String value) throws IOException {
59+
if (value == null || value.isEmpty())
60+
out.nullValue();
61+
else
62+
STRING.write(out, value);
63+
}
64+
65+
@Override
66+
public String read(final JsonReader in) throws IOException {
67+
String value = STRING.read(in);
68+
return value != null && value.isEmpty() ? null : value; // Read empty strings as null
69+
}
70+
})
71+
.create();
72+
73+
private static @Nullable URLConnection getConnection(String address) {
74+
URI uri;
75+
URL url;
76+
try {
77+
uri = new URI(address);
78+
url = uri.toURL();
79+
} catch (MalformedURLException | URISyntaxException e) {
80+
SimpleLogger.getGlobal().error("Malformed URL: " + address);
81+
SimpleLogger.getGlobal().error(e::printStackTrace);
82+
return null;
83+
}
84+
85+
try {
86+
int timeout = 5 * 1000;
87+
int max_redirects = 3;
88+
89+
URLConnection con = null;
90+
for (int x = 0; x < max_redirects; x++) {
91+
con = url.openConnection();
92+
con.setConnectTimeout(timeout);
93+
con.setReadTimeout(timeout);
94+
95+
if (con instanceof HttpURLConnection) {
96+
HttpURLConnection hcon = (HttpURLConnection) con;
97+
hcon.setRequestProperty("User-Agent", "MinecraftMaven");
98+
hcon.setRequestProperty("accept", "application/json");
99+
hcon.setInstanceFollowRedirects(false);
100+
101+
int res = hcon.getResponseCode();
102+
if (res == HttpURLConnection.HTTP_MOVED_PERM || res == HttpURLConnection.HTTP_MOVED_TEMP) {
103+
String location = hcon.getHeaderField("Location");
104+
hcon.disconnect();
105+
if (x == max_redirects - 1) {
106+
SimpleLogger.getGlobal().error("Invalid number of redirects: " + location);
107+
return null;
108+
} else {
109+
//System.out.println("Following redirect: " + location);
110+
uri = uri.resolve(location);
111+
url = uri.toURL();
112+
}
113+
} else if (res == 404) {
114+
// File not found
115+
return null;
116+
} else {
117+
break;
118+
}
119+
} else {
120+
break;
121+
}
122+
}
123+
return con;
124+
} catch (IOException e) {
125+
SimpleLogger.getGlobal().error("Failed to establish connection to " + address);
126+
SimpleLogger.getGlobal().error(e::printStackTrace);
127+
return null;
128+
}
129+
}
130+
131+
/**
132+
* Downloads a string from the given URL, effectively acting as {@code curl}.
133+
*
134+
* @param url The URL to download from
135+
* @return The downloaded string, or {@code null} if the download failed
136+
*/
137+
public static @Nullable String downloadString(String url) {
138+
try {
139+
URLConnection connection = getConnection(url);
140+
if (connection != null) {
141+
try (InputStream stream = connection.getInputStream();
142+
ByteArrayOutputStream out = new ByteArrayOutputStream();
143+
) {
144+
byte[] buf = new byte[1024];
145+
int n;
146+
while ((n = stream.read(buf)) > 0) {
147+
out.write(buf, 0, n);
148+
}
149+
150+
return new String(out.toByteArray(), StandardCharsets.UTF_8);
151+
}
152+
}
153+
} catch (IOException e) {
154+
SimpleLogger.getGlobal().warn("Failed to download " + url);
155+
SimpleLogger.getGlobal().warn(e::printStackTrace);
156+
}
157+
return null;
158+
}
159+
160+
/**
161+
* Downloads a file from the given URL into the target file, effectively acting as {@code wget}.
162+
*
163+
* @param target The file to download to
164+
* @param url The URL to download from
165+
* @return {@code true} if the download was successful
166+
*/
167+
public static boolean downloadFile(File target, String url) {
168+
try {
169+
ensureParent(target);
170+
SimpleLogger.getGlobal().quiet("Downloading " + url);
171+
172+
URLConnection connection = getConnection(url);
173+
if (connection != null) {
174+
Files.copy(connection.getInputStream(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
175+
return true;
176+
}
177+
} catch (IOException e) {
178+
SimpleLogger.getGlobal().warn("Failed to download " + url);
179+
SimpleLogger.getGlobal().warn(e::printStackTrace);
180+
}
181+
return false;
182+
}
183+
184+
private static void ensureParent(File path) {
185+
File parent = path.getAbsoluteFile().getParentFile();
186+
if (parent != null && !parent.exists())
187+
parent.mkdirs();
188+
}
189+
}

settings.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44

55
rootProject.name = 'Utils'
66

7-
include 'hash-utils', 'json-data-utils', 'file-utils', 'log-utils'
7+
include 'hash-utils', 'json-data-utils', 'file-utils', 'log-utils', 'download-utils'
88

99
dependencyResolutionManagement {
1010
versionCatalogs {

0 commit comments

Comments
 (0)