From e87c550f2b42bd2568bc2abb7b86fa3158a06868 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Tue, 27 Dec 2016 18:54:23 -0500 Subject: [PATCH] Use an etag for caching instead of md5. Release for 1.11.2 --- build.gradle | 2 +- build.number | 4 +- .../hdskins/HDProfileTexture.java | 25 --- .../voxelmodpack/hdskins/HDSkinManager.java | 24 +-- .../hdskins/ThreadDownloadImageETag.java | 171 ++++++++++++++++++ .../com/minelittlepony/MineLittlePony.java | 1 - src/main/java/com/minelittlepony/Pony.java | 3 + 7 files changed, 183 insertions(+), 47 deletions(-) delete mode 100644 src/hdskins/java/com/voxelmodpack/hdskins/HDProfileTexture.java create mode 100644 src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java diff --git a/build.gradle b/build.gradle index 44bc1d7f..678d119e 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ apply plugin: 'net.minecraftforge.gradle.liteloader' apply plugin: 'org.spongepowered.mixin' group = 'com.minelittlepony' -version = '1.11.2.0-SNAPSHOT' +version = '1.11.2.0' description = 'Mine Little Pony' targetCompatibility = 1.8 diff --git a/build.number b/build.number index 7362ebe9..b1272ef9 100644 --- a/build.number +++ b/build.number @@ -1,3 +1,3 @@ #Build Number for ANT. Do not edit! -#Sat Dec 24 04:16:32 EST 2016 -build.number=295 +#Tue Dec 27 18:55:42 EST 2016 +build.number=300 diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/HDProfileTexture.java b/src/hdskins/java/com/voxelmodpack/hdskins/HDProfileTexture.java deleted file mode 100644 index 0961a525..00000000 --- a/src/hdskins/java/com/voxelmodpack/hdskins/HDProfileTexture.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.voxelmodpack.hdskins; - -import com.mojang.authlib.minecraft.MinecraftProfileTexture; - -import javax.annotation.Nullable; -import java.util.Map; - -/** - * Profile texture with a custom hash which is not the file name. - */ -public class HDProfileTexture extends MinecraftProfileTexture { - - private String hash; - - public HDProfileTexture(String url, @Nullable String hash, Map metadata) { - super(url, metadata); - this.hash = hash; - } - - @Override - public String getHash() { - return this.hash == null ? super.getHash() : this.hash; - } - -} diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java index 3018b976..14be5fc0 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java @@ -1,12 +1,10 @@ package com.voxelmodpack.hdskins; -import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.io.Resources; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; @@ -21,7 +19,6 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger; import com.voxelmodpack.hdskins.resource.SkinResourceManager; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.IImageBuffer; -import net.minecraft.client.renderer.ThreadDownloadImageData; import net.minecraft.client.renderer.texture.ITextureObject; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.resources.DefaultPlayerSkin; @@ -38,7 +35,6 @@ import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; -import java.net.URL; import java.util.List; import java.util.Map; import java.util.Optional; @@ -127,9 +123,11 @@ public final class HDSkinManager implements IResourceManagerReloadListener { String skinDir = type.toString().toLowerCase() + "s/"; final ResourceLocation skin = new ResourceLocation("hdskins", skinDir + texture.getHash()); - File file2 = new File(LiteLoader.getAssetsDirectory(), "skins/" + skinDir + texture.getHash().substring(0, 2) + "/" + texture.getHash()); + File file2 = new File(LiteLoader.getAssetsDirectory(), "hd/" + skinDir + texture.getHash().substring(0, 2) + "/" + texture.getHash()); + final IImageBuffer imagebufferdownload = type == Type.SKIN ? new ImageBufferDownloadHD() : null; - ThreadDownloadImageData threaddownloadimagedata = new ThreadDownloadImageData(file2, texture.getUrl(), + + ITextureObject texObject = new ThreadDownloadImageETag(file2, texture.getUrl(), DefaultPlayerSkin.getDefaultSkinLegacy(), new IImageBuffer() { @Nonnull @@ -152,7 +150,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener { }); // schedule texture loading on the main thread. - TextureLoader.loadTexture(skin, threaddownloadimagedata); + TextureLoader.loadTexture(skin, texObject); } } @@ -171,9 +169,8 @@ public final class HDSkinManager implements IResourceManagerReloadListener { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Type type : Type.values()) { String url = getCustomTextureURLForId(type, uuid); - String hash = getTextureHash(type, uuid); - builder.put(type, new HDProfileTexture(url, hash, null)); + builder.put(type, new MinecraftProfileTexture(url, null)); } Map textures = builder.build(); @@ -224,15 +221,6 @@ public final class HDSkinManager implements IResourceManagerReloadListener { return getCustomTextureURLForId(type, uuid, false); } - private String getTextureHash(Type type, String uuid) { - try { - URL url = new URL(getCustomTextureURLForId(type, uuid) + ".md5"); - return Resources.asCharSource(url, Charsets.UTF_8).readFirstLine(); - } catch (IOException e) { - return null; - } - } - public void setEnabled(boolean enabled) { this.enabled = enabled; } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java b/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java new file mode 100644 index 00000000..07853f15 --- /dev/null +++ b/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java @@ -0,0 +1,171 @@ +package com.voxelmodpack.hdskins; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; +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.commons.io.FileUtils; +import org.apache.http.Header; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +public class ThreadDownloadImageETag extends SimpleTexture { + private static final Logger LOGGER = LogManager.getLogger(); + private static final AtomicInteger THREAD_ID = new AtomicInteger(0); + @Nonnull + private final File cacheFile; + private final File 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; + this.eTagFile = new File(cacheFile.getParentFile(), cacheFile.getName() + ".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; + } + } + } + + public int getGlTextureId() { + this.checkTextureUploaded(); + return super.getGlTextureId(); + } + + private void setBufferedImage(BufferedImage bufferedImageIn) { + this.bufferedImage = bufferedImageIn; + + if (this.imageBuffer != null) { + this.imageBuffer.skinAvailable(); + } + } + + @Nullable + public BufferedImage getBufferedImage() { + return bufferedImage; + } + + public void loadTexture(IResourceManager resourceManager) throws IOException { + if (this.bufferedImage == null && this.textureLocation != null) { + super.loadTexture(resourceManager); + } + + if (this.imageThread == null) { + loadTexture(); + } + } + + private void loadTexture() { + this.imageThread = new Thread("Texture Downloader #" + THREAD_ID.incrementAndGet()) { + @Override + public void run() { + HttpResponse response = null; + try { + HttpClient client = HttpClientBuilder.create().build(); + response = client.execute(new HttpGet(ThreadDownloadImageETag.this.imageUrl)); + if (checkEtag(response)) { + LOGGER.debug("Loading http texture from local cache ({})", cacheFile); + + try { + bufferedImage = ImageIO.read(cacheFile); + + if (imageBuffer != null) { + setBufferedImage(imageBuffer.parseUserSkin(bufferedImage)); + } + } catch (IOException ioexception) { + LOGGER.error("Couldn't load skin {}", cacheFile, ioexception); + loadTextureFromServer(response); + } + } else { + loadTextureFromServer(response); + } + } catch (IOException e) { + LOGGER.error("Couldn't load skin {} ", imageUrl, e); + } finally { + if (response != null) + EntityUtils.consumeQuietly(response.getEntity()); + } + } + }; + this.imageThread.setDaemon(true); + this.imageThread.start(); + + } + + private boolean checkEtag(HttpResponse response) { + try { + if (cacheFile.isFile()) { + String localEtag = Files.readFirstLine(eTagFile, Charsets.UTF_8); + 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; + } + } + + protected void loadTextureFromServer(HttpResponse response) { + LOGGER.debug("Downloading http texture from {} to {}", imageUrl, cacheFile); + + try { + + if (response.getStatusLine().getStatusCode() / 100 == 2) { + BufferedImage bufferedimage; + + FileUtils.copyInputStreamToFile(response.getEntity().getContent(), cacheFile); + bufferedimage = ImageIO.read(cacheFile); + + Header etag = response.getFirstHeader(HttpHeaders.ETAG); + if (etag != null) { + FileUtils.write(eTagFile, etag.getValue(), Charsets.UTF_8); + } + if (imageBuffer != null) { + bufferedimage = imageBuffer.parseUserSkin(bufferedimage); + } + + setBufferedImage(bufferedimage); + } + } catch (Exception exception) { + LOGGER.error("Couldn\'t download http texture", exception); + } + } +} + diff --git a/src/main/java/com/minelittlepony/MineLittlePony.java b/src/main/java/com/minelittlepony/MineLittlePony.java index 9e7e8f19..ad80ed87 100644 --- a/src/main/java/com/minelittlepony/MineLittlePony.java +++ b/src/main/java/com/minelittlepony/MineLittlePony.java @@ -83,7 +83,6 @@ public class MineLittlePony { void postInit(Minecraft minecraft) { - HDSkinManager.clearSkinCache(); HDSkinManager manager = HDSkinManager.INSTANCE; manager.setSkinUrl(SKIN_SERVER_URL); manager.setGatewayURL(GATEWAY_URL); diff --git a/src/main/java/com/minelittlepony/Pony.java b/src/main/java/com/minelittlepony/Pony.java index 455d1c9b..997cc79d 100644 --- a/src/main/java/com/minelittlepony/Pony.java +++ b/src/main/java/com/minelittlepony/Pony.java @@ -4,6 +4,7 @@ import com.minelittlepony.model.PMAPI; import com.minelittlepony.model.PlayerModel; import com.minelittlepony.util.PonyFields; import com.voxelmodpack.hdskins.DynamicTextureImage; +import com.voxelmodpack.hdskins.ThreadDownloadImageETag; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.AbstractClientPlayer; import net.minecraft.client.renderer.ThreadDownloadImageData; @@ -79,6 +80,8 @@ public class Pony { MineLittlePony.logger.debug("Successfully reflected downloadedImage from texture object", e); // this.checkSkin(skinImage); } + } else if (e2 instanceof ThreadDownloadImageETag) { + skinImage = ((ThreadDownloadImageETag) e2).getBufferedImage(); } else if (e2 instanceof DynamicTextureImage) { skinImage = ((DynamicTextureImage) e2).getImage(); }