Use an etag for caching instead of md5.

Release for 1.11.2
This commit is contained in:
Matthew Messinger 2016-12-27 18:54:23 -05:00
parent 50e29823d7
commit e87c550f2b
7 changed files with 183 additions and 47 deletions

View file

@ -24,7 +24,7 @@ apply plugin: 'net.minecraftforge.gradle.liteloader'
apply plugin: 'org.spongepowered.mixin' apply plugin: 'org.spongepowered.mixin'
group = 'com.minelittlepony' group = 'com.minelittlepony'
version = '1.11.2.0-SNAPSHOT' version = '1.11.2.0'
description = 'Mine Little Pony' description = 'Mine Little Pony'
targetCompatibility = 1.8 targetCompatibility = 1.8

View file

@ -1,3 +1,3 @@
#Build Number for ANT. Do not edit! #Build Number for ANT. Do not edit!
#Sat Dec 24 04:16:32 EST 2016 #Tue Dec 27 18:55:42 EST 2016
build.number=295 build.number=300

View file

@ -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<String, String> metadata) {
super(url, metadata);
this.hash = hash;
}
@Override
public String getHash() {
return this.hash == null ? super.getHash() : this.hash;
}
}

View file

@ -1,12 +1,10 @@
package com.voxelmodpack.hdskins; package com.voxelmodpack.hdskins;
import com.google.common.base.Charsets;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.io.Resources;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
@ -21,7 +19,6 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
import com.voxelmodpack.hdskins.resource.SkinResourceManager; import com.voxelmodpack.hdskins.resource.SkinResourceManager;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.IImageBuffer; 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.ITextureObject;
import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.DefaultPlayerSkin; import net.minecraft.client.resources.DefaultPlayerSkin;
@ -38,7 +35,6 @@ import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -127,9 +123,11 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
String skinDir = type.toString().toLowerCase() + "s/"; String skinDir = type.toString().toLowerCase() + "s/";
final ResourceLocation skin = new ResourceLocation("hdskins", skinDir + texture.getHash()); 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; 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(), DefaultPlayerSkin.getDefaultSkinLegacy(),
new IImageBuffer() { new IImageBuffer() {
@Nonnull @Nonnull
@ -152,7 +150,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
}); });
// schedule texture loading on the main thread. // 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<Type, MinecraftProfileTexture> builder = ImmutableMap.builder(); ImmutableMap.Builder<Type, MinecraftProfileTexture> builder = ImmutableMap.builder();
for (Type type : Type.values()) { for (Type type : Type.values()) {
String url = getCustomTextureURLForId(type, uuid); 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<Type, MinecraftProfileTexture> textures = builder.build(); Map<Type, MinecraftProfileTexture> textures = builder.build();
@ -224,15 +221,6 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
return getCustomTextureURLForId(type, uuid, false); 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) { public void setEnabled(boolean enabled) {
this.enabled = enabled; this.enabled = enabled;
} }

View file

@ -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);
}
}
}

View file

@ -83,7 +83,6 @@ public class MineLittlePony {
void postInit(Minecraft minecraft) { void postInit(Minecraft minecraft) {
HDSkinManager.clearSkinCache();
HDSkinManager manager = HDSkinManager.INSTANCE; HDSkinManager manager = HDSkinManager.INSTANCE;
manager.setSkinUrl(SKIN_SERVER_URL); manager.setSkinUrl(SKIN_SERVER_URL);
manager.setGatewayURL(GATEWAY_URL); manager.setGatewayURL(GATEWAY_URL);

View file

@ -4,6 +4,7 @@ import com.minelittlepony.model.PMAPI;
import com.minelittlepony.model.PlayerModel; import com.minelittlepony.model.PlayerModel;
import com.minelittlepony.util.PonyFields; import com.minelittlepony.util.PonyFields;
import com.voxelmodpack.hdskins.DynamicTextureImage; import com.voxelmodpack.hdskins.DynamicTextureImage;
import com.voxelmodpack.hdskins.ThreadDownloadImageETag;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer; import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.renderer.ThreadDownloadImageData; import net.minecraft.client.renderer.ThreadDownloadImageData;
@ -79,6 +80,8 @@ public class Pony {
MineLittlePony.logger.debug("Successfully reflected downloadedImage from texture object", e); MineLittlePony.logger.debug("Successfully reflected downloadedImage from texture object", e);
// this.checkSkin(skinImage); // this.checkSkin(skinImage);
} }
} else if (e2 instanceof ThreadDownloadImageETag) {
skinImage = ((ThreadDownloadImageETag) e2).getBufferedImage();
} else if (e2 instanceof DynamicTextureImage) { } else if (e2 instanceof DynamicTextureImage) {
skinImage = ((DynamicTextureImage) e2).getImage(); skinImage = ((DynamicTextureImage) e2).getImage();
} }