mirror of
https://github.com/MineLittlePony/MineLittlePony.git
synced 2024-11-22 20:47:59 +01:00
Fix error handling and fix skin server response handling
This commit is contained in:
parent
89e24f3047
commit
9b90e9bfb6
12 changed files with 113 additions and 414 deletions
|
@ -51,13 +51,7 @@ import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Base64;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
@ -123,7 +117,14 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
for (SkinServer server : skinServers) {
|
for (SkinServer server : skinServers) {
|
||||||
try {
|
try {
|
||||||
if (!server.getFeatures().contains(Feature.SYNTHETIC)) {
|
if (!server.getFeatures().contains(Feature.SYNTHETIC)) {
|
||||||
server.loadProfileData(profile).getTextures().forEach(textureMap::putIfAbsent);
|
server.loadProfileData(profile).getTextures().forEach((k, v) -> {
|
||||||
|
try {
|
||||||
|
Type t = Type.valueOf(k.toUpperCase(Locale.ROOT));
|
||||||
|
if (t != null) {
|
||||||
|
textureMap.put(t, v);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {}
|
||||||
|
});
|
||||||
if (textureMap.size() == Type.values().length) {
|
if (textureMap.size() == Type.values().length) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,7 +232,7 @@ public class SkinUploader implements Closeable {
|
||||||
if (throwable instanceof AuthenticationUnavailableException) {
|
if (throwable instanceof AuthenticationUnavailableException) {
|
||||||
offline = true;
|
offline = true;
|
||||||
} else if (throwable instanceof InvalidCredentialsException) {
|
} else if (throwable instanceof InvalidCredentialsException) {
|
||||||
setError("hdskins.error.session");
|
setError("Invalid session: Please try restarting Minecraft");
|
||||||
} else if (throwable instanceof AuthenticationException) {
|
} else if (throwable instanceof AuthenticationException) {
|
||||||
throttlingNeck = true;
|
throttlingNeck = true;
|
||||||
} else if (throwable instanceof HttpException) {
|
} else if (throwable instanceof HttpException) {
|
||||||
|
|
|
@ -2,13 +2,14 @@ package com.voxelmodpack.hdskins.resources;
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
|
||||||
import com.voxelmodpack.hdskins.resources.texture.ISkinAvailableCallback;
|
import com.voxelmodpack.hdskins.resources.texture.ISkinAvailableCallback;
|
||||||
import com.voxelmodpack.hdskins.resources.texture.ImageBufferDownloadHD;
|
import com.voxelmodpack.hdskins.resources.texture.ImageBufferDownloadHD;
|
||||||
|
import com.voxelmodpack.hdskins.server.TexturePayload;
|
||||||
|
|
||||||
import net.minecraft.client.resources.SkinManager;
|
import net.minecraft.client.resources.SkinManager;
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -19,19 +20,22 @@ import javax.annotation.Nullable;
|
||||||
*/
|
*/
|
||||||
public class PreviewTextureManager {
|
public class PreviewTextureManager {
|
||||||
|
|
||||||
private final Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures;
|
private final Map<String, MinecraftProfileTexture> textures;
|
||||||
|
|
||||||
public PreviewTextureManager(MinecraftTexturesPayload payload) {
|
public PreviewTextureManager(TexturePayload payload) {
|
||||||
this.textures = payload.getTextures();
|
this.textures = payload.getTextures();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public PreviewTexture getPreviewTexture(ResourceLocation location, MinecraftProfileTexture.Type type, ResourceLocation def, @Nullable SkinManager.SkinAvailableCallback callback) {
|
public PreviewTexture getPreviewTexture(ResourceLocation location, MinecraftProfileTexture.Type type, ResourceLocation def, @Nullable SkinManager.SkinAvailableCallback callback) {
|
||||||
if (!textures.containsKey(type)) {
|
|
||||||
|
String key = type.name().toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
|
if (!textures.containsKey(key)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftProfileTexture texture = textures.get(type);
|
MinecraftProfileTexture texture = textures.get(key);
|
||||||
ISkinAvailableCallback buff = new ImageBufferDownloadHD(type, () -> {
|
ISkinAvailableCallback buff = new ImageBufferDownloadHD(type, () -> {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.skinAvailable(type, location, new MinecraftProfileTexture(texture.getUrl(), Maps.newHashMap()));
|
callback.skinAvailable(type, location, new MinecraftProfileTexture(texture.getUrl(), Maps.newHashMap()));
|
||||||
|
|
|
@ -1,222 +0,0 @@
|
||||||
package com.voxelmodpack.hdskins.resources.texture;
|
|
||||||
|
|
||||||
import com.voxelmodpack.hdskins.util.MoreHttpResponses;
|
|
||||||
import net.minecraft.client.renderer.IImageBuffer;
|
|
||||||
import net.minecraft.client.renderer.texture.SimpleTexture;
|
|
||||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
|
||||||
import net.minecraft.client.resources.IResourceManager;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
import org.apache.http.Header;
|
|
||||||
import org.apache.http.HttpHeaders;
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.HttpStatus;
|
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.client.methods.HttpHead;
|
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClients;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* Do not use. This will be removed in a later update.
|
|
||||||
* Now that legacy includes the etag in the hash, it is no longer required to save it to disk.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class ThreadDownloadImageETag extends SimpleTexture implements IBufferedTexture {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger();
|
|
||||||
private static final AtomicInteger THREAD_ID = new AtomicInteger(0);
|
|
||||||
private static CloseableHttpClient client = HttpClients.createSystem();
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private final Path cacheFile;
|
|
||||||
private final Path eTagFile;
|
|
||||||
private final String imageUrl;
|
|
||||||
@Nullable
|
|
||||||
private final IImageBuffer imageBuffer;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private BufferedImage bufferedImage;
|
|
||||||
@Nullable
|
|
||||||
private Thread imageThread;
|
|
||||||
private boolean textureUploaded;
|
|
||||||
|
|
||||||
public ThreadDownloadImageETag(@Nonnull File cacheFileIn, String imageUrlIn, ResourceLocation defLocation, @Nullable IImageBuffer imageBufferIn) {
|
|
||||||
super(defLocation);
|
|
||||||
this.cacheFile = cacheFileIn.toPath();
|
|
||||||
this.eTagFile = cacheFile.resolveSibling(cacheFile.getFileName() + ".etag");
|
|
||||||
this.imageUrl = imageUrlIn;
|
|
||||||
this.imageBuffer = imageBufferIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTextureUploaded() {
|
|
||||||
if (!this.textureUploaded) {
|
|
||||||
if (this.bufferedImage != null) {
|
|
||||||
if (this.textureLocation != null) {
|
|
||||||
this.deleteGlTexture();
|
|
||||||
}
|
|
||||||
|
|
||||||
TextureUtil.uploadTextureImage(super.getGlTextureId(), this.bufferedImage);
|
|
||||||
this.textureUploaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getGlTextureId() {
|
|
||||||
this.checkTextureUploaded();
|
|
||||||
return super.getGlTextureId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setBufferedImage(@Nonnull BufferedImage bufferedImageIn) {
|
|
||||||
this.bufferedImage = bufferedImageIn;
|
|
||||||
|
|
||||||
if (this.imageBuffer != null) {
|
|
||||||
this.imageBuffer.skinAvailable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public BufferedImage getBufferedImage() {
|
|
||||||
return bufferedImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadTexture(IResourceManager resourceManager) throws IOException {
|
|
||||||
if (this.bufferedImage == null && this.textureLocation != null) {
|
|
||||||
super.loadTexture(resourceManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.imageThread == null) {
|
|
||||||
this.imageThread = new Thread(this::loadTexture, "Texture Downloader #" + THREAD_ID.incrementAndGet());
|
|
||||||
this.imageThread.setDaemon(true);
|
|
||||||
this.imageThread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadTexture() {
|
|
||||||
switch (checkLocalCache()) {
|
|
||||||
case GONE:
|
|
||||||
clearCache();
|
|
||||||
break;
|
|
||||||
case OK:
|
|
||||||
case NOPE:
|
|
||||||
LOGGER.debug("Loading http texture from local cache ({})", cacheFile);
|
|
||||||
try {
|
|
||||||
// e-tag check passed. Load the local file
|
|
||||||
setLocalCache();
|
|
||||||
break;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Nope. Local cache is corrupt. Re-download it.
|
|
||||||
// fallthrough to load from network
|
|
||||||
LOGGER.error("Couldn't load skin {}", cacheFile, e);
|
|
||||||
}
|
|
||||||
case OUTDATED:
|
|
||||||
loadTextureFromServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void setLocalCache() throws IOException {
|
|
||||||
if (Files.isRegularFile(cacheFile)) {
|
|
||||||
try (InputStream in = Files.newInputStream(cacheFile)) {
|
|
||||||
BufferedImage image = ImageIO.read(in);
|
|
||||||
if (imageBuffer != null) {
|
|
||||||
image = imageBuffer.parseUserSkin(image);
|
|
||||||
}
|
|
||||||
setBufferedImage(image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearCache() {
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(this.cacheFile);
|
|
||||||
Files.deleteIfExists(this.eTagFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum State {
|
|
||||||
OUTDATED,
|
|
||||||
GONE,
|
|
||||||
NOPE,
|
|
||||||
OK
|
|
||||||
}
|
|
||||||
|
|
||||||
private State checkLocalCache() {
|
|
||||||
try (CloseableHttpResponse response = client.execute(new HttpHead(imageUrl))) {
|
|
||||||
int code = response.getStatusLine().getStatusCode();
|
|
||||||
if (code == HttpStatus.SC_NOT_FOUND) {
|
|
||||||
return State.GONE;
|
|
||||||
}
|
|
||||||
if (code != HttpStatus.SC_OK) {
|
|
||||||
return State.NOPE;
|
|
||||||
}
|
|
||||||
return checkETag(response) ? State.OK : State.OUTDATED;
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOGGER.error("Couldn't load skin {} ", imageUrl, e);
|
|
||||||
return State.NOPE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkETag(HttpResponse response) {
|
|
||||||
try {
|
|
||||||
if (Files.isRegularFile(cacheFile)) {
|
|
||||||
String localETag = Files.lines(eTagFile).limit(1).findFirst().orElse("");
|
|
||||||
Header remoteETag = response.getFirstHeader(HttpHeaders.ETAG);
|
|
||||||
// true if no remote etag or does match
|
|
||||||
return remoteETag == null || localETag.equals(remoteETag.getValue());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// it failed, so re-fetch.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadTextureFromServer() {
|
|
||||||
LOGGER.debug("Downloading http texture from {} to {}", imageUrl, cacheFile);
|
|
||||||
try (MoreHttpResponses resp = MoreHttpResponses.execute(client, new HttpGet(imageUrl))) {
|
|
||||||
if (resp.ok()) {
|
|
||||||
// write the image to disk
|
|
||||||
Files.createDirectories(cacheFile.getParent());
|
|
||||||
Files.copy(resp.inputStream(), cacheFile);
|
|
||||||
|
|
||||||
try (InputStream in = Files.newInputStream(cacheFile)) {
|
|
||||||
BufferedImage bufferedimage = ImageIO.read(in);
|
|
||||||
|
|
||||||
// maybe write the etag to disk
|
|
||||||
Header eTag = resp.response().getFirstHeader(HttpHeaders.ETAG);
|
|
||||||
if (eTag != null) {
|
|
||||||
Files.write(eTagFile, Collections.singleton(eTag.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageBuffer != null) {
|
|
||||||
bufferedimage = imageBuffer.parseUserSkin(bufferedimage);
|
|
||||||
}
|
|
||||||
setBufferedImage(bufferedimage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception exception) {
|
|
||||||
LOGGER.error("Couldn\'t download http texture", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import com.google.common.collect.ImmutableMap.Builder;
|
||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
import com.voxelmodpack.hdskins.gui.Feature;
|
import com.voxelmodpack.hdskins.gui.Feature;
|
||||||
import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
|
import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
|
||||||
|
@ -43,13 +42,13 @@ public class BethlehemSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
|
public TexturePayload loadProfileData(GameProfile profile) throws IOException {
|
||||||
try (MoreHttpResponses response = new NetClient("GET", getPath(profile)).send()) {
|
try (MoreHttpResponses response = new NetClient("GET", getPath(profile)).send()) {
|
||||||
if (!response.ok()) {
|
if (!response.ok()) {
|
||||||
throw new HttpException(response.response());
|
throw new HttpException(response.response());
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.unwrapAsJson(MinecraftTexturesPayload.class);
|
return response.requireOk().json(TexturePayload.class, "Invalid texture payload");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,9 +65,7 @@ public class BethlehemSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
try (MoreHttpResponses response = client.send()) {
|
try (MoreHttpResponses response = client.send()) {
|
||||||
if (!response.ok()) {
|
response.requireOk();
|
||||||
throw response.exception();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,11 @@ import com.google.gson.annotations.Expose;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
import com.voxelmodpack.hdskins.gui.Feature;
|
import com.voxelmodpack.hdskins.gui.Feature;
|
||||||
import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
|
import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
|
||||||
import com.voxelmodpack.hdskins.util.MoreHttpResponses;
|
import com.voxelmodpack.hdskins.util.MoreHttpResponses;
|
||||||
import com.voxelmodpack.hdskins.util.NetClient;
|
import com.voxelmodpack.hdskins.util.NetClient;
|
||||||
import com.voxelmodpack.hdskins.util.TexturesPayloadBuilder;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.http.Header;
|
import org.apache.http.Header;
|
||||||
|
@ -53,39 +51,39 @@ public class LegacySkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MinecraftTexturesPayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException {
|
public TexturePayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException {
|
||||||
SkinServer.verifyServerConnection(Minecraft.getMinecraft().getSession(), SERVER_ID);
|
SkinServer.verifyServerConnection(Minecraft.getMinecraft().getSession(), SERVER_ID);
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(gateway)) {
|
if (Strings.isNullOrEmpty(gateway)) {
|
||||||
throw gatewayUnsupported();
|
throw gatewayUnsupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class);
|
Map<String, MinecraftProfileTexture> map = new HashMap<>();
|
||||||
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
||||||
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null));
|
map.put(type.name(), new MinecraftProfileTexture(getPath(gateway, type, profile), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return TexturesPayloadBuilder.createTexturesPayload(profile, map);
|
return new TexturePayload(profile, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
|
public TexturePayload loadProfileData(GameProfile profile) throws IOException {
|
||||||
ImmutableMap.Builder<MinecraftProfileTexture.Type, MinecraftProfileTexture> builder = ImmutableMap.builder();
|
ImmutableMap.Builder<String, MinecraftProfileTexture> builder = ImmutableMap.builder();
|
||||||
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
||||||
|
|
||||||
String url = getPath(address, type, profile);
|
String url = getPath(address, type, profile);
|
||||||
try {
|
try {
|
||||||
builder.put(type, loadProfileTexture(profile, url));
|
builder.put(type.name(), loadProfileTexture(profile, url));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.trace("Couldn't find texture for {} at {}. Does it exist?", profile.getName(), url, e);
|
logger.trace("Couldn't find texture for {} at {}. Does it exist?", profile.getName(), url, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = builder.build();
|
Map<String, MinecraftProfileTexture> map = builder.build();
|
||||||
if (map.isEmpty()) {
|
if (map.isEmpty()) {
|
||||||
throw new HttpException(String.format("No textures found for %s at %s", profile, this.address), 404, null);
|
throw new HttpException(String.format("No textures found for %s at %s", profile, this.address), 404, null);
|
||||||
}
|
}
|
||||||
return TexturesPayloadBuilder.createTexturesPayload(profile, map);
|
return new TexturePayload(profile, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MinecraftProfileTexture loadProfileTexture(GameProfile profile, String url) throws IOException {
|
private MinecraftProfileTexture loadProfileTexture(GameProfile profile, String url) throws IOException {
|
||||||
|
@ -125,7 +123,7 @@ public class LegacySkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
MoreHttpResponses resp = client.send();
|
MoreHttpResponses resp = client.send();
|
||||||
String response = resp.text();
|
String response = resp.reader().readLine();
|
||||||
|
|
||||||
if (response.startsWith("ERROR: ")) {
|
if (response.startsWith("ERROR: ")) {
|
||||||
response = response.substring(7);
|
response = response.substring(7);
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.voxelmodpack.hdskins.server;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||||
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
|
||||||
import com.mumfrey.liteloader.modconfig.Exposable;
|
import com.mumfrey.liteloader.modconfig.Exposable;
|
||||||
import com.voxelmodpack.hdskins.gui.Feature;
|
import com.voxelmodpack.hdskins.gui.Feature;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
@ -29,7 +28,7 @@ public interface SkinServer extends Exposable {
|
||||||
*
|
*
|
||||||
* @throws IOException If any authentication or network error occurs.
|
* @throws IOException If any authentication or network error occurs.
|
||||||
*/
|
*/
|
||||||
MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException;
|
TexturePayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously uploads a skin to this server.
|
* Synchronously uploads a skin to this server.
|
||||||
|
@ -51,7 +50,7 @@ public interface SkinServer extends Exposable {
|
||||||
* @throws AuthenticationException
|
* @throws AuthenticationException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
default MinecraftTexturesPayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException {
|
default TexturePayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException {
|
||||||
return loadProfileData(profile);
|
return loadProfileData(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import com.google.gson.JsonSerializationContext;
|
import com.google.gson.JsonSerializationContext;
|
||||||
import com.google.gson.JsonSerializer;
|
import com.google.gson.JsonSerializer;
|
||||||
import com.voxelmodpack.hdskins.HDSkinManager;
|
|
||||||
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
|
@ -6,7 +6,6 @@ import com.google.gson.annotations.Expose;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
import com.voxelmodpack.hdskins.gui.Feature;
|
import com.voxelmodpack.hdskins.gui.Feature;
|
||||||
import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
|
import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
|
||||||
|
@ -54,14 +53,9 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException {
|
public TexturePayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException {
|
||||||
try (MoreHttpResponses response = MoreHttpResponses.execute(HTTP_CLIENT, new HttpGet(getTexturesURI(profile)))) {
|
try (MoreHttpResponses response = MoreHttpResponses.execute(HTTP_CLIENT, new HttpGet(getTexturesURI(profile)))) {
|
||||||
|
return response.requireOk().json(TexturePayload.class, "Invalid texture payload");
|
||||||
if (response.ok()) {
|
|
||||||
return response.unwrapAsJson(MinecraftTexturesPayload.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new HttpException(response.response());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,9 +125,7 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
|
|
||||||
private void upload(HttpUriRequest request) throws IOException {
|
private void upload(HttpUriRequest request) throws IOException {
|
||||||
try (MoreHttpResponses response = MoreHttpResponses.execute(HTTP_CLIENT, request)) {
|
try (MoreHttpResponses response = MoreHttpResponses.execute(HTTP_CLIENT, request)) {
|
||||||
if (!response.ok()) {
|
response.requireOk();
|
||||||
throw response.exception();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +155,7 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
.setUri(getHandshakeURI())
|
.setUri(getHandshakeURI())
|
||||||
.addParameter("name", name)
|
.addParameter("name", name)
|
||||||
.build())) {
|
.build())) {
|
||||||
return resp.unwrapAsJson(AuthHandshake.class);
|
return resp.requireOk().json(AuthHandshake.class, "Invalid handshake response");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +165,7 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
.addParameter("name", name)
|
.addParameter("name", name)
|
||||||
.addParameter("verifyToken", String.valueOf(verifyToken))
|
.addParameter("verifyToken", String.valueOf(verifyToken))
|
||||||
.build())) {
|
.build())) {
|
||||||
return resp.unwrapAsJson(AuthResponse.class);
|
return resp.requireOk().json(AuthResponse.class, "Invalid auth response");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import com.google.gson.Gson;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||||
import com.mojang.authlib.minecraft.*;
|
import com.mojang.authlib.minecraft.*;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
import com.voxelmodpack.hdskins.HDSkinManager;
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
import com.voxelmodpack.hdskins.gui.Feature;
|
import com.voxelmodpack.hdskins.gui.Feature;
|
||||||
|
@ -53,9 +52,7 @@ public class YggdrasilSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException {
|
public TexturePayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException {
|
||||||
|
|
||||||
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures = new HashMap<>();
|
|
||||||
|
|
||||||
Minecraft client = Minecraft.getMinecraft();
|
Minecraft client = Minecraft.getMinecraft();
|
||||||
MinecraftSessionService session = client.getSessionService();
|
MinecraftSessionService session = client.getSessionService();
|
||||||
|
@ -68,13 +65,16 @@ public class YggdrasilSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
profile = newProfile;
|
profile = newProfile;
|
||||||
|
|
||||||
|
Map<String, MinecraftProfileTexture> textures = new HashMap<>();
|
||||||
try {
|
try {
|
||||||
textures.putAll(session.getTextures(profile, requireSecure));
|
session.getTextures(profile, requireSecure).forEach((k, v) -> {
|
||||||
|
textures.put(k.name(), v);
|
||||||
|
});
|
||||||
} catch (InsecureTextureException e) {
|
} catch (InsecureTextureException e) {
|
||||||
HDSkinManager.logger.error(e);
|
HDSkinManager.logger.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TexturesPayloadBuilder.createTexturesPayload(profile, textures);
|
return new TexturePayload(profile, textures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package com.voxelmodpack.hdskins.util;
|
package com.voxelmodpack.hdskins.util;
|
||||||
|
|
||||||
import com.google.common.io.ByteStreams;
|
|
||||||
import com.google.common.io.CharStreams;
|
import com.google.common.io.CharStreams;
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
import com.voxelmodpack.hdskins.HDSkinManager;
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
|
import com.voxelmodpack.hdskins.server.HttpException;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
@ -17,12 +17,9 @@ import org.apache.http.message.BasicNameValuePair;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for getting different response types from a http response.
|
* Utility class for getting different response types from a http response.
|
||||||
|
@ -33,89 +30,6 @@ public interface MoreHttpResponses extends AutoCloseable {
|
||||||
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
CloseableHttpResponse response();
|
|
||||||
|
|
||||||
default boolean ok() {
|
|
||||||
return responseCode() < HttpStatus.SC_MULTIPLE_CHOICES;
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean json() {
|
|
||||||
return "application/json".contentEquals(contentType().getMimeType());
|
|
||||||
}
|
|
||||||
|
|
||||||
default int responseCode() {
|
|
||||||
return response().getStatusLine().getStatusCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
default Optional<HttpEntity> entity() {
|
|
||||||
return Optional.ofNullable(response().getEntity());
|
|
||||||
}
|
|
||||||
|
|
||||||
default ContentType contentType() {
|
|
||||||
return entity()
|
|
||||||
.map(ContentType::get)
|
|
||||||
.orElse(ContentType.DEFAULT_TEXT);
|
|
||||||
}
|
|
||||||
|
|
||||||
default InputStream inputStream() throws IOException {
|
|
||||||
return response().getEntity().getContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
default BufferedReader reader() throws IOException {
|
|
||||||
return new BufferedReader(new InputStreamReader(inputStream(), StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
default byte[] bytes() throws IOException {
|
|
||||||
try (InputStream input = inputStream()) {
|
|
||||||
return ByteStreams.toByteArray(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default String text() throws IOException {
|
|
||||||
try (BufferedReader reader = reader()) {
|
|
||||||
return CharStreams.toString(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default Stream<String> lines() throws IOException {
|
|
||||||
try (BufferedReader reader = reader()) {
|
|
||||||
return reader.lines();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default <T> T json(Class<T> type, String errorMessage) throws IOException {
|
|
||||||
return json((Type)type, errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
default <T> T json(Type type, String errorMessage) throws IOException {
|
|
||||||
if (!json()) {
|
|
||||||
String text = text();
|
|
||||||
HDSkinManager.logger.error(errorMessage, text);
|
|
||||||
throw new IOException(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
try (BufferedReader reader = reader()) {
|
|
||||||
return GSON.fromJson(reader, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default <T> T unwrapAsJson(Type type) throws IOException {
|
|
||||||
if (ok()) {
|
|
||||||
return json(type, "Server returned a non-json response!");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
default IOException exception() throws IOException {
|
|
||||||
return new IOException(json(JsonObject.class, "Server error wasn't in json: {}").get("message").getAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default void close() throws IOException {
|
|
||||||
response().close();
|
|
||||||
}
|
|
||||||
|
|
||||||
static MoreHttpResponses execute(CloseableHttpClient client, HttpUriRequest request) throws IOException {
|
static MoreHttpResponses execute(CloseableHttpClient client, HttpUriRequest request) throws IOException {
|
||||||
CloseableHttpResponse response = client.execute(request);
|
CloseableHttpResponse response = client.execute(request);
|
||||||
return () -> response;
|
return () -> response;
|
||||||
|
@ -128,4 +42,71 @@ public interface MoreHttpResponses extends AutoCloseable {
|
||||||
)
|
)
|
||||||
.toArray(NameValuePair[]::new);
|
.toArray(NameValuePair[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CloseableHttpResponse response();
|
||||||
|
|
||||||
|
default boolean contentTypeMatches(String contentType) {
|
||||||
|
return contentType.contentEquals(entity()
|
||||||
|
.map(ContentType::get)
|
||||||
|
.orElse(ContentType.DEFAULT_TEXT)
|
||||||
|
.getMimeType()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
default int responseCode() {
|
||||||
|
return response().getStatusLine().getStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Optional<HttpEntity> entity() {
|
||||||
|
return Optional.ofNullable(response().getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
default BufferedReader reader() throws IOException {
|
||||||
|
return new BufferedReader(new InputStreamReader(response().getEntity().getContent(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
default String text() throws IOException {
|
||||||
|
try (BufferedReader reader = reader()) {
|
||||||
|
return CharStreams.toString(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> T json(Class<T> type, String errorMessage) throws IOException {
|
||||||
|
|
||||||
|
if (!contentTypeMatches("application/json")) {
|
||||||
|
String text = text();
|
||||||
|
HDSkinManager.logger.error(errorMessage, text);
|
||||||
|
throw new HttpException(text, responseCode(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = text();
|
||||||
|
|
||||||
|
T t = GSON.fromJson(text, type);
|
||||||
|
if (t == null) {
|
||||||
|
throw new HttpException(errorMessage + "\n " + text, responseCode(), null);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean ok() {
|
||||||
|
return responseCode() < HttpStatus.SC_MULTIPLE_CHOICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
default MoreHttpResponses requireOk() throws IOException {
|
||||||
|
if (!ok()) {
|
||||||
|
JsonObject json = json(JsonObject.class, "Server did not respond correctly. Status Code " + responseCode());
|
||||||
|
if (json.has("message")) {
|
||||||
|
throw new HttpException(json.get("message").getAsString(), responseCode(), null);
|
||||||
|
} else {
|
||||||
|
throw new HttpException(json.toString(), responseCode(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() throws IOException {
|
||||||
|
response().close();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
package com.voxelmodpack.hdskins.util;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.mojang.authlib.GameProfile;
|
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
|
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this to build a {@link MinecraftTexturesPayload} object. This is
|
|
||||||
* required because it has no useful constructor. This uses reflection
|
|
||||||
* via Gson to create a new instance and populate the fields.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class TexturesPayloadBuilder {
|
|
||||||
|
|
||||||
private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
|
|
||||||
|
|
||||||
public static MinecraftTexturesPayload createTexturesPayload(GameProfile profile, Map<Type, MinecraftProfileTexture> textures) {
|
|
||||||
// This worked fine as is before I started using sub-classes.
|
|
||||||
MinecraftTexturesPayload payload = gson.fromJson(gson.toJson(new TexturesPayloadBuilder(profile)), MinecraftTexturesPayload.class);
|
|
||||||
payload.getTextures().putAll(textures);
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long timestamp;
|
|
||||||
|
|
||||||
private UUID profileId;
|
|
||||||
private String profileName;
|
|
||||||
|
|
||||||
private boolean isPublic;
|
|
||||||
|
|
||||||
private Map<Type, MinecraftProfileTexture> textures;
|
|
||||||
|
|
||||||
private TexturesPayloadBuilder(GameProfile profile) {
|
|
||||||
profileId = profile.getId();
|
|
||||||
profileName = profile.getName();
|
|
||||||
timestamp = System.currentTimeMillis();
|
|
||||||
|
|
||||||
isPublic = true;
|
|
||||||
|
|
||||||
this.textures = new HashMap<>();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue