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 // Ensure we don't a. spam console, and b. spam download/check requests
if (!brokenResourcePacks.contains(packet.getPackId())) { if (!brokenResourcePacks.contains(packet.getPackId())) {
brokenResourcePacks.add(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?"); "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 // 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() { public ResourcePack create() {
if (this.fallback == null) { if (this.fallback == null) {
try { try {
final Path downloadedPack = ResourcePackLoader.downloadPack(url).whenComplete((pack, throwable) -> { final Path downloadedPack = ResourcePackLoader.downloadPack(url, false).whenComplete((pack, throwable) -> {
if (throwable != null) { if (throwable != null) {
GeyserImpl.getInstance().getLogger().error("Failed to download pack from " + url, throwable); GeyserImpl.getInstance().getLogger().error("Failed to download pack from " + url, throwable);
if (GeyserImpl.getInstance().getConfig().isDebugMode()) { 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"); final Path cachedCdnPacksDirectory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
// Download CDN packs to get the pack uuid's // Download CDN packs to get the pack uuid's
@ -231,11 +231,8 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
return packMap; return packMap;
} }
public static CompletableFuture<@Nullable Path> downloadPack(String url) throws IllegalArgumentException { public static CompletableFuture<@Nullable Path> downloadPack(String url, boolean force) throws IllegalArgumentException {
return WebUtils.checkUrlAndDownloadRemotePack(url, force).whenCompleteAsync((cachedPath, throwable) -> {
//TODO check if our cache pack is fine (size, url hash; head req)
return WebUtils.checkUrlAndDownloadRemotePack(url).whenCompleteAsync((cachedPath, throwable) -> {
if (cachedPath == null) { if (cachedPath == null) {
// already warned about in WebUtils // already warned about in WebUtils
return; return;

Datei anzeigen

@ -34,6 +34,7 @@ import javax.naming.directory.InitialDirContext;
import java.io.*; import java.io.*;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -41,11 +42,15 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class WebUtils { 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 * 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 * If it is, it will download the pack file and return a path to it
* *
* @param url The URL to check * @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 * @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(() -> { return CompletableFuture.supplyAsync(() -> {
try { try {
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); 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(); int size = con.getContentLength();
String type = con.getContentType(); String type = con.getContentType();
@ -123,21 +140,49 @@ public class WebUtils {
return null; return null;
} }
InputStream in = con.getInputStream(); Path packLocation = REMOTE_PACK_CACHE.resolve(url.hashCode() + ".zip");
Path fileLocation = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs").resolve(url.hashCode() + ".zip"); Path packMetadata = packLocation.resolveSibling(url.hashCode() + ".metadata");
Files.copy(in, fileLocation, StandardCopyOption.REPLACE_EXISTING);
if (Files.size(fileLocation) != size) { if (Files.exists(packLocation) && Files.exists(packMetadata)) {
GeyserImpl.getInstance().getLogger().error("Downloaded pack has " + Files.size(fileLocation) + " bytes, expected " + size + " bytes"); try {
Files.delete(fileLocation); 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 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) { } catch (MalformedURLException e) {
throw new IllegalArgumentException("Malformed URL: " + url); 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) { } 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() + ")");
} }
}); });
} }