mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Code cleanup, less futures, more exceptions when needed
This commit is contained in:
parent
507a79eb88
commit
86f645899f
7 changed files with 155 additions and 167 deletions
|
@ -68,7 +68,7 @@ public abstract class PackCodec {
|
|||
* @return the new resource pack
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract ResourcePack create();
|
||||
public abstract ResourcePack create();
|
||||
|
||||
/**
|
||||
* Creates a new pack provider from the given path.
|
||||
|
|
|
@ -36,8 +36,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
* <li>be a direct download link to a .zip or .mcpack resource pack</li>
|
||||
* <li>use the application type `application/zip` and set a correct content length</li>
|
||||
* </ul>
|
||||
*
|
||||
* Additionally, the resource pack must be zipped in a folder enclosing the resource pack, instead of the resource pack being at the root of the zip.
|
||||
*/
|
||||
public abstract class UrlPackCodec extends PackCodec {
|
||||
|
||||
|
|
|
@ -650,8 +650,6 @@ public class GeyserImpl implements GeyserApi {
|
|||
this.erosionUnixListener.close();
|
||||
}
|
||||
|
||||
// todo check
|
||||
//Registries.RESOURCE_PACKS.get().clear();
|
||||
ResourcePackLoader.clear();
|
||||
|
||||
this.setEnabled(false);
|
||||
|
|
|
@ -319,7 +319,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
|
||||
// If a remote pack ends up here, that usually implies that a platform was not able to download the pack
|
||||
if (codec instanceof UrlPackCodec urlPackCodec) {
|
||||
ResourcePackLoader.checkPack(urlPackCodec);
|
||||
ResourcePackLoader.testUrlPack(urlPackCodec);
|
||||
}
|
||||
|
||||
data.setChunkIndex(packet.getChunkIndex());
|
||||
|
|
|
@ -28,56 +28,46 @@ package org.geysermc.geyser.pack.url;
|
|||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
public class GeyserUrlPackCodec extends UrlPackCodec {
|
||||
private final String url;
|
||||
private final String contentKey;
|
||||
private final @NonNull String url;
|
||||
private final @Nullable String contentKey;
|
||||
@Getter
|
||||
private GeyserPathPackCodec fallback;
|
||||
private PathPackCodec fallback;
|
||||
|
||||
public GeyserUrlPackCodec(String url) throws IllegalArgumentException {
|
||||
this(url, "");
|
||||
this(url, null);
|
||||
}
|
||||
|
||||
public GeyserUrlPackCodec(@NonNull String url, @Nullable String contentKey) throws IllegalArgumentException {
|
||||
//noinspection ConstantValue - need to enforce
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("Url cannot be nulL!");
|
||||
}
|
||||
Objects.requireNonNull(url, "url cannot be null");
|
||||
this.url = url;
|
||||
this.contentKey = contentKey == null ? "" : contentKey;
|
||||
this.contentKey = contentKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NonNull [] sha256() {
|
||||
if (this.fallback == null) {
|
||||
throw new IllegalStateException("Fallback pack not initialized! Needs to be created first.");
|
||||
}
|
||||
Objects.requireNonNull(fallback, "must call #create() before attempting to get the sha256!");
|
||||
return fallback.sha256();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
if (this.fallback == null) {
|
||||
throw new IllegalStateException("Fallback pack not initialized! Needs to be created first.");
|
||||
}
|
||||
Objects.requireNonNull(fallback, "must call #create() before attempting to get the size!");
|
||||
return fallback.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException {
|
||||
if (this.fallback == null) {
|
||||
throw new IllegalStateException("Fallback pack not initialized! Needs to be created first.");
|
||||
}
|
||||
Objects.requireNonNull(fallback, "must call #create() before attempting to serialize!!");
|
||||
return fallback.serialize(resourcePack);
|
||||
}
|
||||
|
||||
|
@ -86,17 +76,15 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
|
|||
public ResourcePack create() {
|
||||
if (this.fallback == null) {
|
||||
try {
|
||||
final Path downloadedPack = ResourcePackLoader.downloadPack(url, false).whenComplete((pack, throwable) -> {
|
||||
ResourcePackLoader.downloadPack(url, false).whenComplete((pack, throwable) -> {
|
||||
if (throwable != null) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to download pack from " + url + " due to " + throwable.getMessage());
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
GeyserImpl.getInstance().getLogger().error("full error: " + throwable);
|
||||
}
|
||||
throw new IllegalArgumentException(throwable);
|
||||
} else if (pack != null) {
|
||||
this.fallback = pack;
|
||||
}
|
||||
}).join();
|
||||
this.fallback = new GeyserPathPackCodec(downloadedPack);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Failed to download pack from " + url, e);
|
||||
throw new IllegalArgumentException("Failed to download pack from the url %s (reason: %s)!".formatted(url, e.getMessage()));
|
||||
}
|
||||
}
|
||||
return ResourcePackLoader.readPack(this);
|
||||
|
@ -109,6 +97,6 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
|
|||
|
||||
@Override
|
||||
public @NonNull String contentKey() {
|
||||
return this.contentKey;
|
||||
return this.contentKey != null ? contentKey : "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,13 @@
|
|||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
|
@ -49,12 +52,9 @@ import java.nio.file.FileSystems;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -70,7 +70,9 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
* Used to keep track of remote resource packs that the client rejected.
|
||||
* If a client rejects such a pack, it falls back to the old method, and Geyser serves a cached variant.
|
||||
*/
|
||||
private static final Set<UrlPackCodec> brokenPacks = new HashSet<>();
|
||||
private static final Cache<String, UrlPackCodec> CACHED_FAILED_PACKS = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
|
||||
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
|
||||
|
||||
|
@ -169,9 +171,6 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
*/
|
||||
public static GeyserResourcePack readPack(GeyserUrlPackCodec codec) throws IllegalArgumentException {
|
||||
Path path = codec.getFallback().path();
|
||||
if (!PACK_MATCHER.matches(path)) {
|
||||
throw new IllegalArgumentException("The url " + codec.url() + " did not provide a valid resource pack! Please check the url and try again.");
|
||||
}
|
||||
|
||||
ResourcePackManifest manifest = readManifest(path, codec.url());
|
||||
String contentKey = codec.contentKey();
|
||||
|
@ -216,9 +215,9 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
}
|
||||
|
||||
private Map<String, ResourcePack> loadRemotePacks() {
|
||||
// Unable to make this a static variable, as the test would fail
|
||||
final Path cachedCdnPacksDirectory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
|
||||
|
||||
// Download CDN packs to get the pack uuid's
|
||||
if (!Files.exists(cachedCdnPacksDirectory)) {
|
||||
try {
|
||||
Files.createDirectories(cachedCdnPacksDirectory);
|
||||
|
@ -231,7 +230,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
List<String> remotePackUrls = GeyserImpl.getInstance().getConfig().getResourcePackUrls();
|
||||
Map<String, ResourcePack> packMap = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
for (String url: remotePackUrls) {
|
||||
for (String url : remotePackUrls) {
|
||||
try {
|
||||
GeyserUrlPackCodec codec = new GeyserUrlPackCodec(url);
|
||||
ResourcePack pack = codec.create();
|
||||
|
@ -246,57 +245,65 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
return packMap;
|
||||
}
|
||||
|
||||
public static void checkPack(UrlPackCodec codec) {
|
||||
if (!brokenPacks.contains(codec)) {
|
||||
brokenPacks.add(codec);
|
||||
GeyserImpl.getInstance().getLogger().warning("Received a request for a remote pack that the client should have already downloaded! " +
|
||||
"Is the pack at the URL " + codec.url() + " still available?");
|
||||
/**
|
||||
* Used when a Bedrock client requests a Bedrock resource pack from the server when it should be downloading it
|
||||
* from a remote provider. Since this would be called each time a Bedrock client requests a piece of the Bedrock pack,
|
||||
* this uses a cache to ensure we aren't re-checking a dozen times.
|
||||
*
|
||||
* @param codec the codec of the resource pack that wasn't successfully downloaded by a Bedrock client.
|
||||
*/
|
||||
public static void testUrlPack(UrlPackCodec codec) {
|
||||
if (CACHED_FAILED_PACKS.getIfPresent(codec.url()) == null) {
|
||||
CACHED_FAILED_PACKS.put(codec.url(), codec);
|
||||
GeyserImpl.getInstance().getLogger().warning("A client was not able to download the resource pack at %s. Is it still available? Running check now:");
|
||||
downloadPack(codec.url(), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static CompletableFuture<@Nullable Path> downloadPack(String url, boolean checking) throws IllegalArgumentException {
|
||||
return WebUtils.checkUrlAndDownloadRemotePack(url, checking).whenCompleteAsync((cachedPath, throwable) -> {
|
||||
if (cachedPath == null) {
|
||||
// already warned about in WebUtils
|
||||
return;
|
||||
}
|
||||
public static CompletableFuture<@Nullable PathPackCodec> downloadPack(String url, boolean testing) throws IllegalArgumentException {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Path path = WebUtils.checkUrlAndDownloadRemotePack(url, testing);
|
||||
|
||||
if (throwable != null) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to download resource pack! ", throwable);
|
||||
return;
|
||||
// Already warned about these above
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the pack is a .zip or .mcpack file
|
||||
if (!PACK_MATCHER.matches(cachedPath)) {
|
||||
if (!PACK_MATCHER.matches(path)) {
|
||||
throw new IllegalArgumentException("Invalid pack format! Not a .zip or .mcpack file.");
|
||||
}
|
||||
|
||||
if (checking) {
|
||||
try {
|
||||
Files.delete(cachedPath);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Could not delete debug pack! " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
try (ZipFile zip = new ZipFile(cachedPath.toFile())) {
|
||||
try (ZipFile zip = new ZipFile(path.toFile())) {
|
||||
if (zip.stream().noneMatch(x -> x.getName().contains("manifest.json"))) {
|
||||
throw new IllegalArgumentException(url + " does not contain a manifest file.");
|
||||
throw new IllegalArgumentException("The pack at the url " + url + " does not contain a manifest file!");
|
||||
}
|
||||
|
||||
// // Check if a "manifest.json" or "pack_manifest.json" file is located directly in the zip... does not work otherwise.
|
||||
// // (something like MyZip.zip/manifest.json) will not, but will if it's a subfolder (MyPack.zip/MyPack/manifest.json)
|
||||
// if (zip.getEntry("manifest.json") != null || zip.getEntry("pack_manifest.json") != null) {
|
||||
// GeyserImpl.getInstance().getLogger().debug("The remote resource pack from " + url + " contains a manifest.json file at the root of the zip file. " +
|
||||
// "This is not supported for remote packs, and will cause Bedrock clients to fall back to request the pack from the server. " +
|
||||
// "Please put the pack file in a subfolder, and provide that zip in the URL.");
|
||||
// }
|
||||
// Check if a "manifest.json" or "pack_manifest.json" file is located directly in the zip... does not work otherwise.
|
||||
// (something like MyZip.zip/manifest.json) will not, but will if it's a subfolder (MyPack.zip/MyPack/manifest.json)
|
||||
if (zip.getEntry("manifest.json") != null || zip.getEntry("pack_manifest.json") != null) {
|
||||
if (testing) {
|
||||
GeyserImpl.getInstance().getLogger().info("The remote resource pack from " + url + " contains a manifest.json file at the root of the zip file. " +
|
||||
"This may not work for remote packs, and could cause Bedrock clients to fall back to request the pack from the server. " +
|
||||
"Please put the pack file in a subfolder, and provide that zip in the URL.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), e);
|
||||
}
|
||||
|
||||
if (testing) {
|
||||
try {
|
||||
Files.delete(path);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Could not delete debug pack! " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return new GeyserPathPackCodec(path);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -305,7 +312,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
|
||||
// Now: let's clean up broken remote packs, so we don't cache them
|
||||
Path location = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("remote_packs");
|
||||
brokenPacks.forEach(codec -> {
|
||||
for (UrlPackCodec codec : CACHED_FAILED_PACKS.asMap().values()) {
|
||||
int hash = codec.url().hashCode();
|
||||
Path packLocation = location.resolve(hash + ".zip");
|
||||
Path packMetadata = packLocation.resolveSibling(hash + ".metadata");
|
||||
|
@ -320,6 +327,6 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not delete broken cached resource packs! " + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,11 +33,7 @@ import org.geysermc.geyser.GeyserLogger;
|
|||
import javax.naming.directory.Attribute;
|
||||
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.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -47,7 +43,6 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class WebUtils {
|
||||
|
||||
|
@ -111,91 +106,93 @@ public class WebUtils {
|
|||
*
|
||||
* @param url The URL to check
|
||||
* @param force If true, the pack will be downloaded even if it is cached to a separate location.
|
||||
* @return Path to the downloaded pack file
|
||||
* @return Path to the downloaded pack file, or null if it was unable to be loaded
|
||||
*/
|
||||
public static CompletableFuture<@Nullable Path> checkUrlAndDownloadRemotePack(String url, boolean force) {
|
||||
public static @Nullable Path checkUrlAndDownloadRemotePack(String url, boolean force) {
|
||||
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
|
||||
try {
|
||||
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
|
||||
|
||||
con.setConnectTimeout(10000);
|
||||
con.setReadTimeout(10000);
|
||||
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().platformName() + "/" + GeyserImpl.VERSION);
|
||||
con.setInstanceFollowRedirects(true);
|
||||
con.setConnectTimeout(10000);
|
||||
con.setReadTimeout(10000);
|
||||
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().platformName() + "/" + GeyserImpl.VERSION);
|
||||
con.setInstanceFollowRedirects(true);
|
||||
|
||||
int responseCode = con.getResponseCode();
|
||||
if (responseCode >= 400) {
|
||||
logger.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();
|
||||
|
||||
if (size <= 0) {
|
||||
logger.error(String.format("Invalid size from remote pack URL: %s (size: %d)", url, size));
|
||||
return null;
|
||||
}
|
||||
|
||||
// This doesn't seem to be a requirement (anymore?). Logging to debug might be interesting though.
|
||||
if (type == null || !type.equals("application/zip")) {
|
||||
logger.debug(String.format("Application type from remote pack URL: %s (type: %s)", url, type));
|
||||
}
|
||||
|
||||
Path packLocation = REMOTE_PACK_CACHE.resolve(url.hashCode() + ".zip");
|
||||
Path packMetadata = packLocation.resolveSibling(url.hashCode() + ".metadata");
|
||||
|
||||
if (Files.exists(packLocation) && Files.exists(packMetadata) && !force) {
|
||||
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()) {
|
||||
logger.debug("Using cached pack for " + url);
|
||||
return packLocation;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to read cached pack metadata: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Path downloadLocation = force ? REMOTE_PACK_CACHE.resolve(url.hashCode() + "_debug") : packLocation;
|
||||
Files.copy(con.getInputStream(), downloadLocation, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// This needs to match as the client fails to download the pack otherwise
|
||||
if (Files.size(downloadLocation) != size) {
|
||||
GeyserImpl.getInstance().getLogger().error(String.format("Size mismatch with resource pack at url: %s. Downloaded pack has %s bytes, expected %s bytes!", url, Files.size(packLocation), size));
|
||||
Files.delete(downloadLocation);
|
||||
return null;
|
||||
}
|
||||
|
||||
// "Force" runs when the client rejected a pack. This is done for diagnosis of the issue.
|
||||
if (force) {
|
||||
if (Files.size(packLocation) != Files.size(downloadLocation)) {
|
||||
logger.error("The pack size seems to have changed. If you wish to change the pack at the remote URL, restart/reload Geyser. " +
|
||||
"Changing the pack mid-game can result in clients rejecting the pack, connected clients having different pack, or similar. ");
|
||||
}
|
||||
} else {
|
||||
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 downloadLocation;
|
||||
} 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.getMessage() + ")");
|
||||
int responseCode = con.getResponseCode();
|
||||
if (responseCode >= 400) {
|
||||
throw new IllegalStateException(String.format("Invalid response code from remote pack URL: %s (code: %d)", url, responseCode));
|
||||
}
|
||||
});
|
||||
|
||||
int size = con.getContentLength();
|
||||
String type = con.getContentType();
|
||||
|
||||
if (size <= 0) {
|
||||
throw new IllegalArgumentException(String.format("Invalid size from remote pack URL: %s (size: %d)", url, size));
|
||||
}
|
||||
|
||||
// This doesn't seem to be a requirement (anymore?). Logging to debug might be interesting though.
|
||||
if (type == null || !type.equals("application/zip")) {
|
||||
logger.debug(String.format("Application type from remote pack URL: %s (type: %s)", url, type));
|
||||
}
|
||||
|
||||
Path packLocation = REMOTE_PACK_CACHE.resolve(url.hashCode() + ".zip");
|
||||
Path packMetadata = packLocation.resolveSibling(url.hashCode() + ".metadata");
|
||||
|
||||
// If we downloaded this pack before, reuse it if the ETag matches.
|
||||
if (Files.exists(packLocation) && Files.exists(packMetadata) && !force) {
|
||||
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()) {
|
||||
logger.debug("Using cached pack for " + url);
|
||||
return packLocation;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to read cached pack metadata: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Path downloadLocation = force ? REMOTE_PACK_CACHE.resolve(url.hashCode() + "_debug") : packLocation;
|
||||
Files.copy(con.getInputStream(), downloadLocation, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// This needs to match as the client fails to download the pack otherwise
|
||||
long downloadSize = Files.size(downloadLocation);
|
||||
if (downloadSize != size) {
|
||||
Files.delete(downloadLocation);
|
||||
throw new IllegalStateException("Size mismatch with resource pack at url: %s. Downloaded pack has %s bytes, expected %s bytes"
|
||||
.formatted(url, downloadSize, size));
|
||||
}
|
||||
|
||||
// "Force" runs when the client rejected a pack. This is done for diagnosis of the issue.
|
||||
if (force) {
|
||||
// Check whether existing pack's size matches the newly downloaded packs' size
|
||||
if (Files.size(packLocation) != Files.size(downloadLocation)) {
|
||||
logger.error("""
|
||||
The pack size seems to have changed (%s, expected %s). If you wish to change the pack at the remote URL, restart/reload Geyser.
|
||||
Changing the pack while Geyser is running can result in unexpected issues.
|
||||
""".formatted(Files.size(packLocation), Files.size(downloadLocation)));
|
||||
}
|
||||
} else {
|
||||
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 downloadLocation;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Unable to download resource pack from malformed URL %s! ".formatted(url));
|
||||
} catch (SocketTimeoutException | ConnectException e) {
|
||||
logger.error("Unable to download pack from url %s due to network error! ( %s )".formatted(url, e.getMessage()));
|
||||
logger.debug(e);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to download and save remote resource pack from: %s ( %s )!".formatted(url, e.getMessage()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue