Skip to content

Commit

Permalink
Add loading image from local storage and dumping images to local storage
Browse files Browse the repository at this point in the history
  • Loading branch information
zbx1425 committed Dec 3, 2023
1 parent 69b440a commit 4ddd529
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cn.zbx1425.worldcomment.data;

import cn.zbx1425.worldcomment.data.network.ImageDump;

import java.util.Locale;

public class CommentCommand {
Expand All @@ -8,7 +10,7 @@ public static boolean isCommand(CommentEntry comment) {
return comment.message.startsWith("$SNCMD:");
}

public static void executeCommand(CommentEntry comment, ServerWorldData worldData) {
public static void executeCommandServer(CommentEntry comment, ServerWorldData worldData) {
if (!isCommand(comment)) return;
String commandContent = comment.message.substring(7);
String command = commandContent.split(" ")[0].toLowerCase(Locale.ROOT);
Expand All @@ -21,4 +23,17 @@ public static void executeCommand(CommentEntry comment, ServerWorldData worldDat
}
}
}

public static void executeCommandClient(CommentEntry comment) {
if (!isCommand(comment)) return;
String commandContent = comment.message.substring(7);
String command = commandContent.split(" ")[0].toLowerCase(Locale.ROOT);
String[] args = commandContent.substring(command.length()).trim().split(" ");
switch (command) {
case "imagedumpall" -> {
if (args.length < 1) return;
ImageDump.requestDumpComments(args[0]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void load() throws IOException {
public void insert(CommentEntry newEntry, boolean fromPeer) throws IOException {
if (CommentCommand.isCommand(newEntry)) {
if (isHost) {
CommentCommand.executeCommand(newEntry, this);
CommentCommand.executeCommandServer(newEntry, this);
}
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package cn.zbx1425.worldcomment.data.network;

import cn.zbx1425.worldcomment.Main;
import com.mojang.blaze3d.platform.MemoryTracker;
import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.codec.digest.DigestUtils;
import org.lwjgl.system.MemoryUtil;

import java.io.IOException;
Expand All @@ -16,11 +16,14 @@
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Stream;

public class ImageDownload {

Expand All @@ -30,42 +33,79 @@ public class ImageDownload {

private static final Map<String, ImageState> images = new HashMap<>();

public static AbstractTexture getTexture(String url) {
if (!images.containsKey(url)) {
images.put(url, new ImageState());
NETWORK_EXECUTOR.execute(() -> {
try {
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
HttpResponse<byte[]> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) throw new IOException("HTTP Status " + response.statusCode());
byte[] imageData = response.body();
Minecraft.getInstance().execute(() -> {
DynamicTexture dynamicTexture;
ByteBuffer buffer = MemoryUtil.memByteBuffer(OFF_HEAP_ALLOCATOR.malloc(imageData.length), imageData.length);
try {
buffer.put(imageData);
buffer.rewind();
dynamicTexture = new DynamicTexture(NativeImage.read(buffer));
synchronized (images) {
images.get(url).texture = dynamicTexture;
}
} catch (Throwable ex) {
Main.LOGGER.warn("Cannot download image " + url, ex);
synchronized (images) {
images.get(url).failed = true;
}
} finally {
OFF_HEAP_ALLOCATOR.free(MemoryUtil.memAddress0(buffer));
}
});
} catch (Throwable ex) {
Main.LOGGER.warn("Cannot download image " + url, ex);
synchronized (images) {
images.get(url).failed = true;
}
public static AbstractTexture getTexture(ThumbImage image, boolean thumb) {
try {
byte[] localImageData = getLocalImageData(image.url);
if (localImageData != null) {
if (!images.containsKey(image.url)) {
images.put(image.url, new ImageState());
applyImageData(image.url, localImageData);
}
});
return queryTexture(image.url);
}
} catch (IOException ex) {
Main.LOGGER.warn("Cannot read local image " + image.url, ex);
}

String targetUrl = (thumb && !image.thumbUrl.isEmpty()) ? image.thumbUrl : image.url;
if (!images.containsKey(targetUrl)) {
images.put(targetUrl, new ImageState());
NETWORK_EXECUTOR.execute(() -> downloadImage(targetUrl));
}
return queryTexture(targetUrl);
}

private static void downloadImage(String url) {
try {
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
HttpResponse<byte[]> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) throw new IOException("HTTP Status " + response.statusCode());
byte[] imageData = response.body();
applyImageData(url, imageData);
} catch (Throwable ex) {
Main.LOGGER.warn("Cannot download image " + url, ex);
synchronized (images) {
images.get(url).failed = true;
}
}
}

private static byte[] getLocalImageData(String url) throws IOException {
Path imageBaseDir = Minecraft.getInstance().gameDirectory.toPath().resolve("worldcomment-images");
try (Stream<Path> imageDirs = Files.list(imageBaseDir)) {
for (Path imageDir : imageDirs.toArray(Path[]::new)) {
Path imagePath = imageDir.resolve("url-sha1-" + DigestUtils.sha1Hex(url) + ".png");
if (Files.exists(imagePath)) {
return Files.readAllBytes(imagePath);
}
}
}
return null;
}

private static void applyImageData(String url, byte[] imageData) {
Minecraft.getInstance().execute(() -> {
DynamicTexture dynamicTexture;
ByteBuffer buffer = MemoryUtil.memByteBuffer(OFF_HEAP_ALLOCATOR.malloc(imageData.length), imageData.length);
try {
buffer.put(imageData);
buffer.rewind();
dynamicTexture = new DynamicTexture(NativeImage.read(buffer));
synchronized (images) {
images.get(url).texture = dynamicTexture;
}
} catch (Throwable ex) {
Main.LOGGER.warn("Cannot store image " + url, ex);
synchronized (images) {
images.get(url).failed = true;
}
} finally {
OFF_HEAP_ALLOCATOR.free(MemoryUtil.memAddress0(buffer));
}
});
}

private static AbstractTexture queryTexture(String url) {
synchronized (images) {
ImageState state = images.get(url);
state.onQuery();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package cn.zbx1425.worldcomment.data.network;

import cn.zbx1425.worldcomment.BuildConfig;
import cn.zbx1425.worldcomment.Main;
import cn.zbx1425.worldcomment.data.CommentEntry;
import cn.zbx1425.worldcomment.data.ServerWorldData;
import cn.zbx1425.worldcomment.network.PacketCollectionRequestC2S;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import org.apache.commons.codec.digest.DigestUtils;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class ImageDump {

private static long lastRequestNonce = 0;
private static String lastRequestDirName = "";

private static final Executor NETWORK_EXECUTOR = Executors.newSingleThreadExecutor();
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();

public static void requestDumpComments(String dirName) {
lastRequestNonce = ServerWorldData.SNOWFLAKE.nextId();
lastRequestDirName = dirName;
PacketCollectionRequestC2S.ClientLogics.sendLatest(0, Integer.MAX_VALUE, lastRequestNonce);
}

public static void handleDumpResponse(List<CommentEntry> comments, long nonce) {
if (nonce != lastRequestNonce) return;
Path storeDir = Minecraft.getInstance().gameDirectory.toPath()
.resolve("worldcomment-images").resolve(lastRequestDirName);
try {
if (!Files.isDirectory(storeDir)) Files.createDirectories(storeDir);
} catch (IOException ex) {
Main.LOGGER.error("Cannot create image dump directory", ex);
return;
}

NETWORK_EXECUTOR.execute(() -> {
for (int i = 0; i < comments.size(); i++) {
int finalI = i;
Minecraft.getInstance().execute(() -> {
if (Minecraft.getInstance().player != null) {
Minecraft.getInstance().player.displayClientMessage(
Component.literal("WorldComment: Downloading " + (finalI + 1) + "/" + comments.size()),
true);
}
});
CommentEntry comment = comments.get(i);
String targetUrl = comment.image.url;
if (targetUrl.isEmpty()) continue;
Path filePath = storeDir.resolve("url-sha1-" + DigestUtils.sha1Hex(targetUrl) + ".png");
if (!Files.exists(filePath)) {
try {
byte[] imageData = HTTP_CLIENT.send(
HttpRequest.newBuilder(URI.create(targetUrl))
.GET()
.header("User-Agent",
"Mozilla/5.0 WorldComment/" + BuildConfig.MOD_VERSION + " +https://www.zbx1425.cn")
.build(),
HttpResponse.BodyHandlers.ofByteArray()).body();
Files.write(filePath, imageData);
} catch (IOException | InterruptedException ex) {
Main.LOGGER.warn("Cannot download image " + targetUrl, ex);
}
}
}
Minecraft.getInstance().execute(() -> {
if (Minecraft.getInstance().player != null) {
Minecraft.getInstance().player.displayClientMessage(
Component.literal("WorldComment: Download finished"),
true);
}
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partia
shadowColor
);
if (!comment.image.url.isEmpty()) {
RenderSystem.setShaderTexture(0, ImageDownload.getTexture(comment.image.url).getId());
RenderSystem.setShaderTexture(0, ImageDownload.getTexture(comment.image, false).getId());
} else {
RenderSystem.setShaderTexture(0, new ResourceLocation(Main.MOD_ID, "textures/gui/placeholder-blank.png"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ guiGraphics, getX(), getY(), getWidth(), getHeight(),
}

if (!comment.image.url.isEmpty() && showImage) {
String imageUrl = comment.image.thumbUrl.isEmpty() ? comment.image.url : comment.image.thumbUrl;
RenderSystem.setShaderTexture(0, ImageDownload.getTexture(imageUrl).getId());
RenderSystem.setShaderTexture(0, ImageDownload.getTexture(comment.image, true).getId());
RenderSystem.setShader(GameRenderer::getPositionTexShader);
Matrix4f matrix4f = guiGraphics.pose().last().pose();
BufferBuilder bufferBuilder = Tesselator.getInstance().getBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import cn.zbx1425.worldcomment.Main;
import cn.zbx1425.worldcomment.ServerPlatform;
import cn.zbx1425.worldcomment.data.CommentEntry;
import cn.zbx1425.worldcomment.data.network.ImageDump;
import cn.zbx1425.worldcomment.gui.CommentListScreen;
import io.netty.buffer.Unpooled;
import net.minecraft.client.Minecraft;
Expand Down Expand Up @@ -40,10 +41,13 @@ public static void handle(FriendlyByteBuf buffer) {
if (comment.deleted) continue;
comments.add(comment);
}

Minecraft minecraft = Minecraft.getInstance();
if (minecraft.screen instanceof CommentListScreen) {
((CommentListScreen)minecraft.screen).handleCommentDataUI(comments, nonce);
}

ImageDump.handleDumpResponse(comments, nonce);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public static void send(CommentEntry comment) {
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
buffer.writeResourceLocation(comment.level);
comment.writeBuffer(buffer, false);
if (CommentCommand.isCommand(comment)) {
CommentCommand.executeCommandClient(comment);
}
ClientPlatform.sendPacketToServer(IDENTIFIER, buffer);
}
}
Expand Down
4 changes: 2 additions & 2 deletions fabric/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ shadowJar {
exclude "*.bmp"

configurations = [project.configurations.shadowCommon]
classifier "dev-shadow"
archiveClassifier = "dev-shadow"

dependencies {
it.exclude it.dependency('io.netty:.*')
Expand All @@ -66,7 +66,7 @@ shadowJar {
remapJar {
input.set shadowJar.archiveFile
dependsOn shadowJar
classifier null
archiveClassifier = null
}

components.java {
Expand Down
4 changes: 2 additions & 2 deletions forge/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ shadowJar {
exclude "*.bmp"

configurations = [project.configurations.shadowCommon]
classifier "dev-shadow"
archiveClassifier = "dev-shadow"

dependencies {
it.exclude it.dependency('io.netty:.*')
Expand All @@ -60,7 +60,7 @@ shadowJar {
remapJar {
input.set shadowJar.archiveFile
dependsOn shadowJar
classifier null
archiveClassifier = null
}

components.java {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

0 comments on commit 4ddd529

Please sign in to comment.