mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-30 20:29:19 +00:00
Resolve issues related to re-downloading of failed remote resource packs
This commit is contained in:
@@ -60,11 +60,11 @@ import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
import org.geysermc.geyser.api.pack.option.ResourcePackOption;
|
||||
import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.pack.url.GeyserUrlPackCodec;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
@@ -344,21 +344,18 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourcePack pack = holder.pack();
|
||||
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
||||
PackCodec codec = pack.codec();
|
||||
|
||||
PackCodec codec = holder.codec();
|
||||
// If a remote pack ends up here, that usually implies that a client was not able to download the pack
|
||||
if (codec instanceof UrlPackCodec urlPackCodec) {
|
||||
ResourcePackLoader.testRemotePack(session, urlPackCodec, packet.getPackId(), packet.getPackVersion());
|
||||
|
||||
if (!resourcePackLoadEvent.value(pack.uuid(), ResourcePackOption.Type.FALLBACK, true)) {
|
||||
if (codec instanceof GeyserUrlPackCodec urlPackCodec) {
|
||||
ResourcePackLoader.testRemotePack(session, urlPackCodec, holder);
|
||||
if (!resourcePackLoadEvent.value(holder.uuid(), ResourcePackOption.Type.FALLBACK, true)) {
|
||||
session.disconnect("Unable to provide downloaded resource pack. Contact an administrator!");
|
||||
currentlySendingChunks = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
||||
data.setChunkIndex(packet.getChunkIndex());
|
||||
data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setPackVersion(packet.getPackVersion());
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import lombok.With;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
@@ -32,6 +33,7 @@ import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@With
|
||||
public record GeyserResourcePack(
|
||||
@NonNull PackCodec codec,
|
||||
@NonNull ResourcePackManifest manifest,
|
||||
|
||||
@@ -25,11 +25,17 @@
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import lombok.With;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.api.pack.option.PriorityOption;
|
||||
import org.geysermc.geyser.pack.option.OptionHolder;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@With
|
||||
public record ResourcePackHolder(
|
||||
@NonNull GeyserResourcePack pack,
|
||||
@NonNull OptionHolder optionHolder
|
||||
@@ -42,4 +48,16 @@ public record ResourcePackHolder(
|
||||
public ResourcePack resourcePack() {
|
||||
return this.pack;
|
||||
}
|
||||
|
||||
public PackCodec codec() {
|
||||
return this.pack.codec();
|
||||
}
|
||||
|
||||
public UUID uuid() {
|
||||
return this.pack.uuid();
|
||||
}
|
||||
|
||||
public ResourcePackManifest.Version version() {
|
||||
return pack.manifest().header().version();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,14 @@ package org.geysermc.geyser.pack.url;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackHolder;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
@@ -46,6 +50,12 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
private GeyserUrlPackCodec(@NonNull String url, PathPackCodec fallback) {
|
||||
Objects.requireNonNull(url);
|
||||
this.fallback = fallback;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NonNull [] sha256() {
|
||||
Objects.requireNonNull(fallback, "must call #create() before attempting to get the sha256!");
|
||||
@@ -87,4 +97,43 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
|
||||
public @NonNull String url() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether Geyser's "mirror" of the remote pack needs to be updated.
|
||||
* This is triggered if a Bedrock client is unable to download a pack
|
||||
* @param holder the current resource pack holder with the "originally" known manifest
|
||||
*/
|
||||
public void testForChanges(ResourcePackHolder holder) {
|
||||
ResourcePackLoader.downloadPack(url, true)
|
||||
.thenAccept(backingPathCodec -> {
|
||||
GeyserResourcePack updatedPack = ResourcePackLoader.readPack(backingPathCodec.path())
|
||||
.contentKey(holder.pack().contentKey())
|
||||
.build();
|
||||
if (updatedPack.uuid().equals(holder.uuid())) {
|
||||
var currentVersion = holder.version().toString();
|
||||
var updatedVersion = updatedPack.manifest().header().version().toString();
|
||||
if (currentVersion.equals(updatedVersion)) {
|
||||
GeyserImpl.getInstance().getLogger().info("No version or pack change detected: Was the resource pack server down?");
|
||||
return;
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack version (%s, old version %s) for pack at %s!"
|
||||
.formatted(currentVersion, updatedVersion, url));
|
||||
}
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack at the url %s!".formatted(url));
|
||||
Registries.RESOURCE_PACKS.get().remove(holder.uuid());
|
||||
}
|
||||
|
||||
// Update to new url pack codec (same url, updated fallback), and keep content key
|
||||
GeyserResourcePack pack = updatedPack.withCodec(new GeyserUrlPackCodec(url, backingPathCodec));
|
||||
// Keep the pack options that were previously set
|
||||
Registries.RESOURCE_PACKS.get().put(holder.uuid(), holder.withPack(pack));
|
||||
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), throwable);
|
||||
Registries.RESOURCE_PACKS.get().remove(holder.uuid());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -80,7 +79,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
* If a client rejects such a pack, it falls back to the old method, and Geyser serves a cached variant.
|
||||
*/
|
||||
private static final Cache<String, UrlPackCodec> CACHED_FAILED_PACKS = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.expireAfterWrite(15, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
|
||||
@@ -268,77 +267,23 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
*
|
||||
* @param codec the codec of the resource pack that wasn't successfully downloaded by a Bedrock client.
|
||||
*/
|
||||
public static void testRemotePack(GeyserSession session, UrlPackCodec codec, UUID packId, String packVersion) {
|
||||
public static void testRemotePack(GeyserSession session, GeyserUrlPackCodec codec, ResourcePackHolder holder) {
|
||||
if (CACHED_FAILED_PACKS.getIfPresent(codec.url()) == null) {
|
||||
String url = codec.url();
|
||||
CACHED_FAILED_PACKS.put(url, codec);
|
||||
CACHED_FAILED_PACKS.put(codec.url(), codec);
|
||||
GeyserImpl.getInstance().getLogger().warning(
|
||||
"Bedrock client (%s, playing on %s) was not able to download the resource pack at %s. Checking for changes now:"
|
||||
.formatted(session.bedrockUsername(), session.getClientData().getDeviceOs().name(), codec.url())
|
||||
);
|
||||
|
||||
downloadPack(codec.url(), true).whenComplete((pathPackCodec, e) -> {
|
||||
if (e != null) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", url), e);
|
||||
if (GeyserImpl.getInstance().getLogger().isDebug()) {
|
||||
e.printStackTrace();
|
||||
if (pathPackCodec != null) {
|
||||
deleteFile(pathPackCodec.path());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GeyserResourcePack newPack = readPack(pathPackCodec.path()).build();
|
||||
if (newPack.uuid().equals(packId)) {
|
||||
if (packVersion.equals(newPack.manifest().header().version().toString())) {
|
||||
GeyserImpl.getInstance().getLogger().info("No version or pack change detected: Was the resource pack server down?");
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack version (%s, old version %s) for pack at %s!"
|
||||
.formatted(packVersion, newPack.manifest().header().version().toString(), url));
|
||||
}
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().info("Detected a new resource pack at the url %s!".formatted(url));
|
||||
}
|
||||
|
||||
// This should be safe to do as we're not directly using registries to read packs.
|
||||
// Instead, they're cached per-session in the SessionLoadResourcePacks event
|
||||
Registries.RESOURCE_PACKS.get().remove(packId);
|
||||
Registries.RESOURCE_PACKS.get().put(newPack.uuid(), ResourcePackHolder.of(newPack));
|
||||
|
||||
if (codec instanceof GeyserUrlPackCodec geyserUrlPackCodec
|
||||
&& geyserUrlPackCodec.getFallback() != null) {
|
||||
Path path = geyserUrlPackCodec.getFallback().path();
|
||||
try {
|
||||
GeyserImpl.getInstance().getScheduledThread().schedule(() -> {
|
||||
CACHED_FAILED_PACKS.invalidate(codec.url());
|
||||
deleteFile(path);
|
||||
}, 5, TimeUnit.MINUTES);
|
||||
} catch (RejectedExecutionException exception) {
|
||||
// No scheduling here, probably because we're shutting down?
|
||||
deleteFile(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
codec.testForChanges(holder);
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteFile(Path path) {
|
||||
if (path.toFile().exists()) {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to delete old pack! " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CompletableFuture<@NonNull PathPackCodec> downloadPack(String url, boolean testing) throws IllegalArgumentException {
|
||||
public static CompletableFuture<@NonNull PathPackCodec> downloadPack(String url, boolean force) throws IllegalArgumentException {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Path path;
|
||||
try {
|
||||
path = WebUtils.downloadRemotePack(url, testing);
|
||||
path = WebUtils.downloadRemotePack(url, force);
|
||||
} catch (Throwable e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -223,7 +224,9 @@ public class WebUtils {
|
||||
con.getHeaderField("ETag"),
|
||||
String.valueOf(con.getLastModified()),
|
||||
downloadLocation.getFileName().toString()
|
||||
));
|
||||
),
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
packMetadata.toFile().setLastModified(System.currentTimeMillis());
|
||||
} catch (IOException e) {
|
||||
Files.delete(packMetadata);
|
||||
|
||||
Reference in New Issue
Block a user