Skip to content

Commit

Permalink
Implement file http cache storage
Browse files Browse the repository at this point in the history
Does not limit cache size
  • Loading branch information
Vinrobot committed Nov 22, 2023
1 parent 5bed29b commit 1024cbf
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 11 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ dependencies {
// WebP support for animated WebP using official Google's native library
modClientImplementation "com.github.Vinrobot.WebPDecoderJN:lib:${project.webpdecoderjn_version}"
include "com.github.Vinrobot.WebPDecoderJN:lib:${rootProject.webpdecoderjn_version}"

modImplementation "org.apache.httpcomponents:httpclient-cache:${project.httpcomponents_version}"
include "org.apache.httpcomponents:httpclient-cache:${project.httpcomponents_version}"
}

processResources {
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ fabric_api_version=0.83.0+1.20
# Dependencies versions
modmenu_version=7.2.1
webpdecoderjn_version=1.4
httpcomponents_version=4.5.14
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

import java.awt.image.BufferedImage;
import java.time.Duration;
import java.util.Objects;

public record BufferedFrame(
BufferedImage image,
Duration duration
) {
NativeFrame toNativeFrame() {
public BufferedFrame {
Objects.requireNonNull(image);
Objects.requireNonNull(duration);
}

public NativeFrame toNativeFrame() {
return new NativeFrame(NativeImageHelper.fromBufferedImage(image), duration);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
package net.vinrobot.mcemote.client.imageio;

import net.vinrobot.mcemote.MinecraftEmote;
import net.vinrobot.mcemote.client.imageio.plugins.gif.GifReader;
import net.vinrobot.mcemote.client.imageio.plugins.webp.WebPReader;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;

import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URI;
import java.time.Duration;
import java.util.List;

public class NativeImageIO {
public static NativeFrame[] readAll(final URL url) throws IOException {
try (final InputStream input = url.openStream()) {
public static NativeFrame[] readAll(final URI uri) throws IOException {
final HttpGet httpGet = new HttpGet(uri);
final CloseableHttpClient client = MinecraftEmote.getInstance().getHttpClient();
try (final CloseableHttpResponse response = client.execute(httpGet);
final InputStream input = response.getEntity().getContent()) {
return readAll(input);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URL;
import java.net.URI;

public class BTTVEmote implements net.vinrobot.mcemote.client.font.Emote {
private static final Logger LOGGER = LoggerFactory.getLogger(BTTVEmote.class);
Expand Down Expand Up @@ -38,6 +38,6 @@ public int getHeight() {

@Override
public NativeFrame[] loadFrames() throws IOException {
return NativeImageIO.readAll(new URL("https://cdn.betterttv.net/emote/" + this.emote.id() + "/1x"));
return NativeImageIO.readAll(URI.create("https://cdn.betterttv.net/emote/" + this.emote.id() + "/1x"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import net.vinrobot.mcemote.client.imageio.NativeImageIO;

import java.io.IOException;
import java.net.URL;
import java.net.URI;
import java.util.Map;

@Environment(EnvType.CLIENT)
Expand Down Expand Up @@ -38,6 +38,6 @@ public int getHeight() {
public NativeFrame[] loadFrames() throws IOException {
final Map<String, String> urls = this.emoticon.urls();
final String url = urls.containsKey("1") ? urls.get("1") : urls.values().iterator().next();
return NativeImageIO.readAll(new URL(url));
return NativeImageIO.readAll(URI.create(url));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import net.vinrobot.mcemote.client.imageio.NativeImageIO;

import java.io.IOException;
import java.net.URL;
import java.net.URI;
import java.util.Comparator;

@Environment(EnvType.CLIENT)
Expand All @@ -25,7 +25,7 @@ private EmoteFile getFile() {
return this.emote.data().host().files().stream()
.filter(f -> "WEBP".equalsIgnoreCase(f.format()))
.sorted(Comparator.comparingInt(EmoteFile::size))
.findFirst().get();
.findFirst().orElseThrow();
}

@Override
Expand All @@ -51,6 +51,6 @@ public NativeFrame[] loadFrames() throws IOException {
final EmoteHost host = data.host();
final EmoteFile file = getFile();
final String url = "https:" + host.url() + "/" + file.name();
return NativeImageIO.readAll(new URL(url));
return NativeImageIO.readAll(URI.create(url));
}
}
20 changes: 20 additions & 0 deletions src/main/java/net/vinrobot/mcemote/MinecraftEmote.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import net.vinrobot.mcemote.config.ConfigurationManager;
import net.vinrobot.mcemote.config.ConfigurationService;
import net.vinrobot.mcemote.config.file.FileConfigurationService;
import net.vinrobot.mcemote.http.FileHttpCacheStorage;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpClientBuilder;

import java.nio.file.Path;

Expand All @@ -13,15 +17,31 @@ public static MinecraftEmote getInstance() {
}

private final ConfigurationManager configManager;
private final CloseableHttpClient httpClient;

protected MinecraftEmote() {
final Path configDir = FabricLoader.getInstance().getConfigDir();
final Path configFile = configDir.resolve("mcemote.json");
final ConfigurationService configService = new FileConfigurationService(configFile);
this.configManager = new ConfigurationManager(configService);

final Path cacheDir = FabricLoader.getInstance().getGameDir()
.resolve("cache")
.resolve("mcemote.httpcache");
final CacheConfig cacheConfig = CacheConfig.custom()
.setMaxObjectSize(1 << 26) // 64 MiB
.build();
this.httpClient = CachingHttpClientBuilder.create()
.setCacheConfig(cacheConfig)
.setHttpCacheStorage(new FileHttpCacheStorage(cacheDir))
.build();
}

public ConfigurationManager getConfigManager() {
return this.configManager;
}

public CloseableHttpClient getHttpClient() {
return this.httpClient;
}
}
89 changes: 89 additions & 0 deletions src/main/java/net/vinrobot/mcemote/http/FileHttpCacheStorage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package net.vinrobot.mcemote.http;

import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheEntrySerializer;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.impl.client.cache.DefaultHttpCacheEntrySerializer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;

import static org.apache.commons.codec.digest.DigestUtils.sha256Hex;

public class FileHttpCacheStorage implements HttpCacheStorage {
private final HttpCacheEntrySerializer serializer = new DefaultHttpCacheEntrySerializer();
private final Path cacheDir;

public FileHttpCacheStorage(final Path cacheDir) {
this.cacheDir = Objects.requireNonNull(cacheDir);
}

@Override
public void putEntry(final String key, final HttpCacheEntry entry) throws IOException {
final Path file = this.computePath(key);
synchronized (this) {
this.writeCacheEntry(entry, file);
}
}

@Override
public HttpCacheEntry getEntry(final String key) throws IOException {
final Path file = this.computePath(key);
try {
synchronized (this) {
return this.readCacheEntry(file);
}
} catch (final NoSuchFileException e) {
return null;
}
}

@Override
public void removeEntry(final String key) throws IOException {
final Path file = this.computePath(key);
synchronized (this) {
Files.deleteIfExists(file);
}
}

@Override
public void updateEntry(final String key, final HttpCacheUpdateCallback callback) throws IOException {
final Path file = this.computePath(key);
synchronized (this) {
final HttpCacheEntry entry = Files.exists(file) ? this.readCacheEntry(file) : null;
final HttpCacheEntry updated = callback.update(entry);
if (updated == null) {
Files.deleteIfExists(file);
} else {
this.writeCacheEntry(updated, file);
}
}
}

private void writeCacheEntry(final HttpCacheEntry entry, final Path path) throws IOException {
final Path parentPath = path.getParent();
if (!Files.exists(parentPath)) {
Files.createDirectories(parentPath);
}
try (final OutputStream os = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
this.serializer.writeTo(entry, os);
}
}

private HttpCacheEntry readCacheEntry(final Path path) throws IOException {
try (final InputStream is = Files.newInputStream(path, StandardOpenOption.READ)) {
return this.serializer.readFrom(is);
}
}

private Path computePath(final String key) {
return this.cacheDir.resolve(sha256Hex(key));
}
}

0 comments on commit 1024cbf

Please sign in to comment.