3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-10-04 00:41:13 +02:00

More robust downloading/caching

- better exception catching
- proper error handling
- caching of packs if size, etag, and last modified are the same
Dieser Commit ist enthalten in:
onebeastchris 2023-12-22 14:39:47 +01:00
Ursprung 626189fa25
Commit f12129986e
4 geänderte Dateien mit 61 neuen und 19 gelöschten Zeilen

Datei anzeigen

@ -284,10 +284,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
// Ensure we don't a. spam console, and b. spam download/check requests
if (!brokenResourcePacks.contains(packet.getPackId())) {
brokenResourcePacks.add(packet.getPackId());
GeyserImpl.getInstance().getLogger().warning("Received a request for a remote pack that the client should have already downloaded!" +
GeyserImpl.getInstance().getLogger().warning("Received a request for a remote pack that the client should have already downloaded! " +
"Is the pack at the URL " + urlPackCodec.url() + " still available?");
// not actually interested in using the download, but this does all the checks we need
ResourcePackLoader.downloadPack(urlPackCodec.url());
ResourcePackLoader.downloadPack(urlPackCodec.url(), true);
}
}

Datei anzeigen

@ -81,7 +81,7 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
public ResourcePack create() {
if (this.fallback == null) {
try {
final Path downloadedPack = ResourcePackLoader.downloadPack(url).whenComplete((pack, throwable) -> {
final Path downloadedPack = ResourcePackLoader.downloadPack(url, false).whenComplete((pack, throwable) -> {
if (throwable != null) {
GeyserImpl.getInstance().getLogger().error("Failed to download pack from " + url, throwable);
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {

Datei anzeigen

@ -200,7 +200,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
}
}
public Map<String, ResourcePack> loadRemotePacks() {
private Map<String, ResourcePack> loadRemotePacks() {
final Path cachedCdnPacksDirectory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
// Download CDN packs to get the pack uuid's
@ -231,11 +231,8 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
return packMap;
}
public static CompletableFuture<@Nullable Path> downloadPack(String url) throws IllegalArgumentException {
//TODO check if our cache pack is fine (size, url hash; head req)
return WebUtils.checkUrlAndDownloadRemotePack(url).whenCompleteAsync((cachedPath, throwable) -> {
public static CompletableFuture<@Nullable Path> downloadPack(String url, boolean force) throws IllegalArgumentException {
return WebUtils.checkUrlAndDownloadRemotePack(url, force).whenCompleteAsync((cachedPath, throwable) -> {
if (cachedPath == null) {
// already warned about in WebUtils
return;

Datei anzeigen

@ -34,6 +34,7 @@ import javax.naming.directory.InitialDirContext;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@ -41,11 +42,15 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class WebUtils {
private static final Path REMOTE_PACK_CACHE = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
/**
* Makes a web request to the given URL and returns the body as a string
*
@ -103,13 +108,25 @@ public class WebUtils {
* If it is, it will download the pack file and return a path to it
*
* @param url The URL to check
* @param force If true, the pack will be downloaded even if it is cached
* @return Path to the downloaded pack file
*/
public static CompletableFuture<@Nullable Path> checkUrlAndDownloadRemotePack(String url) {
public static CompletableFuture<@Nullable Path> checkUrlAndDownloadRemotePack(String url, boolean force) {
return CompletableFuture.supplyAsync(() -> {
try {
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().platformName() + "/" + GeyserImpl.VERSION);
con.setInstanceFollowRedirects(false); // TODO verify
int responseCode = con.getResponseCode();
if (responseCode >= 400) {
GeyserImpl.getInstance().getLogger().error(String.format("Invalid response code from remote pack URL: %s (code: %d)", url, responseCode));
return null;
}
int size = con.getContentLength();
String type = con.getContentType();
@ -123,21 +140,49 @@ public class WebUtils {
return null;
}
InputStream in = con.getInputStream();
Path fileLocation = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs").resolve(url.hashCode() + ".zip");
Files.copy(in, fileLocation, StandardCopyOption.REPLACE_EXISTING);
Path packLocation = REMOTE_PACK_CACHE.resolve(url.hashCode() + ".zip");
Path packMetadata = packLocation.resolveSibling(url.hashCode() + ".metadata");
if (Files.size(fileLocation) != size) {
GeyserImpl.getInstance().getLogger().error("Downloaded pack has " + Files.size(fileLocation) + " bytes, expected " + size + " bytes");
Files.delete(fileLocation);
if (Files.exists(packLocation) && Files.exists(packMetadata)) {
try {
List<String> metadataLines = Files.readAllLines(packMetadata, StandardCharsets.UTF_8);
int cachedSize = Integer.parseInt(metadataLines.get(0));
String cachedEtag = metadataLines.get(1);
long cachedLastModified = Long.parseLong(metadataLines.get(2));
if (cachedSize == size && cachedEtag.equals(con.getHeaderField("ETag")) && cachedLastModified == con.getLastModified()) {
GeyserImpl.getInstance().getLogger().debug("Using cached pack for " + url);
return packLocation;
}
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Failed to read cached pack metadata: " + e.getMessage());
}
}
InputStream in = con.getInputStream();
Files.copy(in, packLocation, StandardCopyOption.REPLACE_EXISTING);
if (Files.size(packLocation) != size) {
GeyserImpl.getInstance().getLogger().error("Downloaded pack has " + Files.size(packLocation) + " bytes, expected " + size + " bytes");
Files.delete(packLocation);
return null;
}
return fileLocation;
try {
Files.write(packMetadata, Arrays.asList(String.valueOf(size), con.getHeaderField("ETag"), String.valueOf(con.getLastModified())));
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Failed to write cached pack metadata: " + e.getMessage());
}
return packLocation;
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Malformed URL: " + url);
} catch (SocketTimeoutException | ConnectException e) {
GeyserImpl.getInstance().getLogger().error("Unable to reach URL: " + url + " (" + e.getMessage() + ")");
return null;
} catch (IOException e) {
throw new RuntimeException("Unable to download and save remote resource pack from: " + url + ")");
e.printStackTrace(); // TODO yeeeeeeeet
throw new RuntimeException("Unable to download and save remote resource pack from: " + url + " (" + e.getMessage() + ")");
}
});
}