Skip to content

Commit

Permalink
Add submitting comments
Browse files Browse the repository at this point in the history
  • Loading branch information
zbx1425 committed Jul 24, 2023
1 parent aa041a5 commit 6b036c6
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 66 deletions.
31 changes: 18 additions & 13 deletions common/src/main/java/cn/zbx1425/worldcomment/data/CommentEntry.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cn.zbx1425.worldcomment.data;

import cn.zbx1425.worldcomment.data.network.ThumbImage;
import io.netty.buffer.Unpooled;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
Expand Down Expand Up @@ -28,22 +30,24 @@ public class CommentEntry {
public int messageType;
public String message;
public boolean deleted;
public String imageUrl;
public ThumbImage image;

public long fileOffset;

public CommentEntry(Player initiator, BlockPos placedAt, int messageType, String message, String imageUrl) {
public CommentEntry(Player initiator, boolean isAnonymous, int messageType, String message) {
id = Database.SNOWFLAKE.nextId();
timestamp = System.currentTimeMillis();
level = initiator.level().dimension().location();
location = placedAt;
region = new ChunkPos(location.getX() >> REGION_SHIFT, location.getZ() >> REGION_SHIFT);
this.initiator = initiator.getGameProfile().getId();
initiatorName = initiator.getGameProfile().getName();
if (isAnonymous) {
this.initiator = Util.NIL_UUID;
initiatorName = "";
} else {
this.initiator = initiator.getGameProfile().getId();
initiatorName = initiator.getGameProfile().getName();
}
this.messageType = messageType;
this.message = message;
deleted = false;
this.imageUrl = imageUrl;
}

public CommentEntry(ResourceLocation level, FriendlyByteBuf src, boolean fromFile) {
Expand All @@ -52,19 +56,19 @@ public CommentEntry(ResourceLocation level, FriendlyByteBuf src, boolean fromFil
timestamp = src.readLong();
this.level = level;
location = src.readBlockPos();
region = new ChunkPos(location.getX() >> REGION_SHIFT, location.getZ() >> REGION_SHIFT);
region = new ChunkPos(location.getX() >> (4 + REGION_SHIFT), location.getZ() >> (4 + REGION_SHIFT));
initiator = src.readUUID();
initiatorName = src.readUtf();
messageType = src.readInt();
message = src.readUtf();
deleted = src.readBoolean();
imageUrl = src.readUtf();
image = new ThumbImage(src.readUtf(), src.readUtf());
if (fromFile) src.skipBytes(16 - (src.readerIndex() % 16));
}

private static UUID uuidFromByteArray(byte[] bytes) {
ByteBuffer bb = ByteBuffer.wrap(bytes);
return new UUID(bb.getLong(), bb.getLong());
public void setLocation(BlockPos location) {
this.location = location;
this.region = new ChunkPos(location.getX() >> (4 + REGION_SHIFT), location.getZ() >> (4 + REGION_SHIFT));
}

public void writeBuffer(FriendlyByteBuf dst, boolean toFile) {
Expand All @@ -76,7 +80,8 @@ public void writeBuffer(FriendlyByteBuf dst, boolean toFile) {
dst.writeInt(messageType);
dst.writeUtf(message);
dst.writeBoolean(deleted);
dst.writeUtf(imageUrl);
dst.writeUtf(image.url);
dst.writeUtf(image.thumbUrl);
if (toFile) dst.writeZero(16 - (dst.writerIndex() % 16));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ public void load() throws IOException {
playerIndex.clear();
timeIndex.clear();
if (db.isHost) {
Files.createDirectories(db.basePath.resolve("regions"));
try (Stream<Path> levelFiles = Files.list(db.basePath.resolve("regions"))) {
Files.createDirectories(db.basePath.resolve("region"));
try (Stream<Path> levelFiles = Files.list(db.basePath.resolve("region"))) {
for (Path levelPath : levelFiles.toList()) {
ResourceLocation dimension = new ResourceLocation(levelPath.getFileName().toString().replace("+", ":"));
try (Stream<Path> files = Files.list(levelPath)) {
for (Path file : files.toList()) {
long region = Long.parseUnsignedLong(file.getFileName().toString().substring(1, 9), 16);
String[] fileNameParts = file.getFileName().toString().split("\\.");
if (fileNameParts.length != 4 || !fileNameParts[3].equals("bin")) continue;
ChunkPos region = new ChunkPos(Integer.parseInt(fileNameParts[1]), Integer.parseInt(fileNameParts[2]));
byte[] fileContent = Files.readAllBytes(file);
loadRegion(dimension, region, fileContent, true);
loadRegion(dimension, region.toLong(), fileContent, true);
}
}
}
Expand All @@ -67,14 +69,14 @@ public void loadRegion(ResourceLocation dimension, long region, byte[] data, boo
}

private Path getLevelPath(ResourceLocation dimension) {
return db.basePath.resolve("regions")
return db.basePath.resolve("region")
.resolve(dimension.getNamespace() + "+" + dimension.getPath());
}

private Path getLevelRegionPath(ResourceLocation dimension, ChunkPos region) {
return db.basePath.resolve("regions")
return db.basePath.resolve("region")
.resolve(dimension.getNamespace() + "+" + dimension.getPath())
.resolve("r" + Long.toHexString(region.toLong()) + ".bin");
.resolve("r." + region.x + "." + region.z + ".bin");
}

public List<CommentEntry> queryRegion(ResourceLocation level, ChunkPos region) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public void tick() {
lastTickTime = currentTime;

BlockPos playerPos = minecraft.player.blockPosition();
int cx = playerPos.getX() >> CommentEntry.REGION_SHIFT;
int cz = playerPos.getZ() >> CommentEntry.REGION_SHIFT;
int cx = playerPos.getX() >> (4 + CommentEntry.REGION_SHIFT);
int cz = playerPos.getZ() >> (4 + CommentEntry.REGION_SHIFT);

List<ChunkPos> regionsToRequest = new ArrayList<>();
for (int x = cx - 1; x <= cx + 1; x++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cn.zbx1425.worldcomment.data.network;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

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.charset.StandardCharsets;
import java.nio.file.Path;

public class ImageHost {

private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();

private static String apiUrl = "https://storage.zbx1425.cn/img-lsky/api/v1/upload";
private static String token = "1|FcYzmMmcCylQjcWkk4GdRvNUgGlbE5e7Eh752WOQ";

public static ThumbImage uploadImage(Path imagePath) throws IOException, InterruptedException {
MimeMultipartData body = MimeMultipartData.newBuilder()
.withCharset(StandardCharsets.UTF_8)
.addFile("file", imagePath, "application/octet-stream")
.addText("strategy_id", "1")
.build();
HttpRequest request = HttpRequest.newBuilder(URI.create(apiUrl))
.header("Content-Type", body.getContentType())
.header("Authorization", "Bearer " + token)
.POST(body.getBodyPublisher())
.build();
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IOException("HTTP response " + response.statusCode());
JsonObject linkObj = JsonParser.parseString(response.body()).getAsJsonObject()
.get("data").getAsJsonObject().get("links").getAsJsonObject();
return new ThumbImage(linkObj.get("url").getAsString(), linkObj.get("thumbnail_url").getAsString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package cn.zbx1425.worldcomment.data.network;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.http.HttpRequest;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

public class MimeMultipartData {

public static class Builder {

private String boundary;
private Charset charset = StandardCharsets.UTF_8;
private List<MimedFile> files = new ArrayList<MimedFile>();
private Map<String, String> texts = new LinkedHashMap<>();

private Builder() {
this.boundary = new BigInteger(128, new Random()).toString();
}

public Builder withCharset(Charset charset) {
this.charset = charset;
return this;
}

public Builder withBoundary(String boundary) {
this.boundary = boundary;
return this;
}

public Builder addFile(String name, Path path, String mimeType) {
this.files.add(new MimedFile(name, path, mimeType));
return this;
}

public Builder addText(String name, String text) {
texts.put(name, text);
return this;
}

public MimeMultipartData build() throws IOException {
MimeMultipartData mimeMultipartData = new MimeMultipartData();
mimeMultipartData.boundary = boundary;

var newline = "\r\n".getBytes(charset);
var byteArrayOutputStream = new ByteArrayOutputStream();
for (var f : files) {
byteArrayOutputStream.write(("--" + boundary).getBytes(charset));
byteArrayOutputStream.write(newline);
byteArrayOutputStream.write(("Content-Disposition: form-data; name=\"" + f.name + "\"; filename=\"" + f.path.getFileName() + "\"").getBytes(charset));
byteArrayOutputStream.write(newline);
byteArrayOutputStream.write(("Content-Type: " + f.mimeType).getBytes(charset));
byteArrayOutputStream.write(newline);
byteArrayOutputStream.write(newline);
byteArrayOutputStream.write(Files.readAllBytes(f.path));
byteArrayOutputStream.write(newline);
}
for (var entry: texts.entrySet()) {
byteArrayOutputStream.write(("--" + boundary).getBytes(charset));
byteArrayOutputStream.write(newline);
byteArrayOutputStream.write(("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"").getBytes(charset));
byteArrayOutputStream.write(newline);
byteArrayOutputStream.write(newline);
byteArrayOutputStream.write(entry.getValue().getBytes(charset));
byteArrayOutputStream.write(newline);
}
byteArrayOutputStream.write(("--" + boundary + "--").getBytes(charset));

mimeMultipartData.bodyPublisher = HttpRequest.BodyPublishers.ofByteArray(byteArrayOutputStream.toByteArray());
return mimeMultipartData;
}

public class MimedFile {

public final String name;
public final Path path;
public final String mimeType;

public MimedFile(String name, Path path, String mimeType) {
this.name = name;
this.path = path;
this.mimeType = mimeType;
}
}
}

private String boundary;
private HttpRequest.BodyPublisher bodyPublisher;

private MimeMultipartData() {
}

public static Builder newBuilder() {
return new Builder();
}

public HttpRequest.BodyPublisher getBodyPublisher() throws IOException {
return bodyPublisher;
}

public String getContentType() {
return "multipart/form-data; boundary=" + boundary;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package cn.zbx1425.worldcomment.data.network;

import cn.zbx1425.worldcomment.data.CommentEntry;
import cn.zbx1425.worldcomment.data.Database;
import cn.zbx1425.worldcomment.network.PacketSubmitCommentC2S;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Screenshot;
import net.minecraft.core.BlockPos;

import java.io.File;
import java.nio.file.Path;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;

public class SubmitDispatcher {

private static final Executor NETWORK_EXECUTOR = Executors.newSingleThreadExecutor();
private static final Long2ObjectMap<SubmitJob> pendingJobs = new Long2ObjectOpenHashMap<>();

public static long addJob(CommentEntry comment, Path imagePath, Consumer<Exception> callback) {
synchronized (pendingJobs) {
long jobId = Database.SNOWFLAKE.nextId();
SubmitJob job = new SubmitJob(comment, imagePath, callback);
pendingJobs.put(jobId, job);
if (imagePath != null) {
NETWORK_EXECUTOR.execute(() -> {
try {
job.setImage(ImageHost.uploadImage(imagePath));
trySendPackage(jobId);
} catch (Exception ex) {
if (job.callback != null) job.callback.accept(ex);
removeJob(jobId);
}
});
}
return jobId;
}
}

public static void placeJobAt(long jobId, BlockPos blockPos) {
synchronized (pendingJobs) {
if (!pendingJobs.containsKey(jobId)) return;
pendingJobs.get(jobId).setLocation(blockPos);
trySendPackage(jobId);
}
}

public static void removeJob(long jobId) {
synchronized (pendingJobs) {
pendingJobs.remove(jobId);
}
}

private static void trySendPackage(long jobId) {
SubmitJob job = pendingJobs.get(jobId);
if (job.isReady()) {
PacketSubmitCommentC2S.ClientLogics.send(job.comment);
if (job.callback != null) job.callback.accept(null);
removeJob(jobId);
}
}

public static void grabScreenshot(Consumer<Path> callback) {
Minecraft.getInstance().execute(() -> {
File targetFile = getAvailableFile();
Screenshot.grab(Minecraft.getInstance().gameDirectory, targetFile.getName(),
Minecraft.getInstance().getMainRenderTarget(),
ignored -> {
callback.accept(targetFile.toPath());
});
});
}

private static File getAvailableFile() {
File screenShotDirectory = new File(Minecraft.getInstance().gameDirectory, Screenshot.SCREENSHOT_DIR);
String s = "WorldComment-" + Util.getFilenameFormattedDateTime();
int i = 1;
File file1;
while ((file1 = new File(screenShotDirectory, s + (i == 1 ? "" : "_" + i) + ".png")).exists()) {
++i;
}
return file1;
}
}
Loading

0 comments on commit 6b036c6

Please sign in to comment.