Compare commits

..

7 commits
0.0.1 ... main

Author SHA1 Message Date
1e99
85d7bc2eef readme from source guide 2024-12-17 18:01:11 +01:00
1e99
9027d2a21c add javadoc to overriding mixin 2024-12-17 17:48:00 +01:00
1e99
a1f0671be0 add prefixes to duck types 2024-12-17 17:34:08 +01:00
1e99
7da03d386d update version 2024-12-17 17:25:34 +01:00
1e99
32dce28118 improve error handling 2024-12-17 17:14:06 +01:00
1e99
4ceec30e0c improve error handling, limit upload size 2024-12-16 18:10:18 +01:00
1e99
ef0d33aac4 improve fabric error handling 2024-12-16 18:10:03 +01:00
12 changed files with 104 additions and 42 deletions

View file

@ -44,5 +44,8 @@ services:
``` ```
### From Source ### From Source
1. Install a JDK: Ensure that you have Java 21 installed.
WIP 2. Clone the git repository: `git clone https://git.1e99.eu/1e99/pixelchat.git`.
3. Change your working directory: `cd ./pixelchat`
4. Build the server JAR: `./gradlew :server:shadowJar`.
5. Run the server: `java -jar ./server/build/libs/server-*-all.jar`.

View file

@ -3,7 +3,7 @@ plugins {
} }
group = 'eu.e99' group = 'eu.e99'
version = '0.0.1' version = project.version
repositories { repositories {
mavenCentral() mavenCentral()

1
gradle.properties Normal file
View file

@ -0,0 +1 @@
version=0.0.2

View file

@ -4,7 +4,7 @@ plugins {
} }
group = 'eu.e99' group = 'eu.e99'
version = '0.0.1' version = project.version
base { base {
archivesName = "pixelchat-fabric-${project.minecraft_version}" archivesName = "pixelchat-fabric-${project.minecraft_version}"

View file

@ -1,5 +1,6 @@
{ {
"pixelchat.uploading": "Uploading", "pixelchat.uploading": "Uploading",
"pixelchat.upload_failed": "Upload failed", "pixelchat.upload_failed.no_error": "Upload failed",
"pixelchat.upload_failed.error": "Upload failed - %s",
"pixelchat.upload_completed": "Upload completed" "pixelchat.upload_completed": "Upload completed"
} }

View file

@ -6,9 +6,9 @@ import java.util.UUID;
public interface BossBars { public interface BossBars {
void add(UUID uuid, ClientBossBar bar); void pixelchat$add(UUID uuid, ClientBossBar bar);
void remove(UUID uuid); void pixelchat$remove(UUID uuid);
ClientBossBar get(UUID uuid); ClientBossBar pixelchat$get(UUID uuid);
} }

View file

@ -43,24 +43,35 @@ public class ImageUploads {
false false
); );
this.minecraft.send(() -> bossBars.add(bossBar.getUuid(), bossBar)); this.minecraft.send(() -> bossBars.pixelchat$add(bossBar.getUuid(), bossBar));
String url = null; String url = null;
Throwable error = null;
for (String host : this.hosts) { for (String host : this.hosts) {
try { try {
url = this.upload(host, path); url = this.upload(host, path);
error = null;
break; break;
} catch (Throwable e) { } catch (Throwable e) {
error = e;
PixelChat.LOGGER.warn("Failed to upload", e); PixelChat.LOGGER.warn("Failed to upload", e);
} }
} }
// IntelliJ complains otherwise // IntelliJ complains otherwise
String finalUrl = url; String finalUrl = url;
Throwable finalError = error;
this.minecraft.send(() -> { this.minecraft.send(() -> {
if (finalUrl == null) { if (finalUrl == null) {
PixelChat.LOGGER.error("No uploader succeeded"); PixelChat.LOGGER.error("No uploader succeeded");
bossBar.setName(Text.translatable("pixelchat.upload_failed"));
Text message = finalError == null ?
Text.translatable("pixelchat.upload_failed.no_error") :
Text.translatable("pixelchat.upload_failed.error", finalError.getMessage());
bossBar.setName(message);
bossBar.setPercent(1.0f); bossBar.setPercent(1.0f);
bossBar.setColor(BossBar.Color.RED); bossBar.setColor(BossBar.Color.RED);
return; return;
@ -84,7 +95,7 @@ public class ImageUploads {
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
} }
this.minecraft.send(() -> bossBars.remove(bossBar.getUuid())); this.minecraft.send(() -> bossBars.pixelchat$remove(bossBar.getUuid()));
} }
private String upload(String host, Path path) throws Throwable { private String upload(String host, Path path) throws Throwable {
@ -99,7 +110,8 @@ public class ImageUploads {
HttpResponse.BodyHandlers.ofString() HttpResponse.BodyHandlers.ofString()
); );
if (res.statusCode() != 201) { if (res.statusCode() != 201) {
throw new RuntimeException(String.format("Failed to upload, expected status 201, got %d", res.statusCode())); String body = res.body();
throw new RuntimeException(String.format("%d: %s", res.statusCode(), body));
} }
String id = res.body(); String id = res.body();

View file

@ -18,17 +18,17 @@ public class BossBarHudMixin implements BossBars {
Map<UUID, ClientBossBar> bossBars; Map<UUID, ClientBossBar> bossBars;
@Override @Override
public void add(UUID uuid, ClientBossBar bar) { public void pixelchat$add(UUID uuid, ClientBossBar bar) {
this.bossBars.put(uuid, bar); this.bossBars.put(uuid, bar);
} }
@Override @Override
public void remove(UUID uuid) { public void pixelchat$remove(UUID uuid) {
this.bossBars.remove(uuid); this.bossBars.remove(uuid);
} }
@Override @Override
public ClientBossBar get(UUID uuid) { public ClientBossBar pixelchat$get(UUID uuid) {
return this.bossBars.get(uuid); return this.bossBars.get(uuid);
} }
} }

View file

@ -16,6 +16,10 @@ public abstract class ChatScreenMixin extends Screen {
super(title); super(title);
} }
/**
* I override this because it doesn't really make a lot of sense to have 2 mods that perform the same thing when files are dragged in.
* If you have issues with this, please contact me.
*/
@Override @Override
public void onFilesDropped(List<Path> paths) { public void onFilesDropped(List<Path> paths) {
for (Path path : paths) { for (Path path : paths) {

View file

@ -4,7 +4,7 @@ plugins {
} }
group = 'eu.e99' group = 'eu.e99'
version = '0.0.1' version = project.version
repositories { repositories {
mavenCentral() mavenCentral()

View file

@ -6,10 +6,10 @@ import io.javalin.http.Context;
import io.javalin.http.HttpStatus; import io.javalin.http.HttpStatus;
import org.apache.commons.imaging.ImageFormats; import org.apache.commons.imaging.ImageFormats;
import org.apache.commons.imaging.Imaging; import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.ImagingException;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalAmount;
@ -19,40 +19,79 @@ public class ImageHandler {
private final Logger logger; private final Logger logger;
private final Storage storage; private final Storage storage;
private final TemporalAmount expireTime; private final TemporalAmount expireTime;
private final int maxImageSize;
public ImageHandler(Logger logger, Storage storage, TemporalAmount expireTime) { public ImageHandler(Logger logger, Storage storage, TemporalAmount expireTime, int maxImageSize) {
this.logger = logger; this.logger = logger;
this.storage = storage; this.storage = storage;
this.expireTime = expireTime; this.expireTime = expireTime;
this.maxImageSize = maxImageSize;
} }
public void uploadImage(Context ctx) throws IOException { public void uploadImage(Context ctx) {
InputStream body = ctx.bodyInputStream(); try {
BufferedImage image = Imaging.getBufferedImage(body); InputStream body = ctx.bodyInputStream();
BufferedImage image = Imaging.getBufferedImage(body);
byte[] png = Imaging.writeImageToBytes(image, ImageFormats.PNG); byte[] png = Imaging.writeImageToBytes(image, ImageFormats.PNG);
Instant expiresAt = Instant.now().plus(this.expireTime); if (png.length > maxImageSize) {
ctx
.status(HttpStatus.CONTENT_TOO_LARGE)
.result("Image too large");
String id = storage.put(png, expiresAt); return;
logger.info("Stored {} bytes as {}", png.length, id); }
ctx. Instant expiresAt = Instant.now().plus(this.expireTime);
status(HttpStatus.CREATED).
contentType(ContentType.TEXT_PLAIN).
result(id);
}
public void downloadImage(Context ctx) throws IOException { String id = storage.put(png, expiresAt);
String id = ctx.pathParam("id"); logger.info("Uploaded {} image bytes as {}", png.length, id);
byte[] png = storage.get(id);
if (png == null) { ctx.
ctx.status(HttpStatus.NOT_FOUND); status(HttpStatus.CREATED).
return; contentType(ContentType.TEXT_PLAIN).
result(id);
} catch (ImagingException | IllegalArgumentException ignored) {
ctx
.status(HttpStatus.BAD_REQUEST)
.result("Unrecognized image format");
} catch (OutOfMemoryError e) {
ctx
.status(HttpStatus.INSUFFICIENT_STORAGE)
.result("Insufficient storage on server");
this.logger.error("Out of memory", e);
} catch (Throwable e) {
ctx
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.result("Internal Server Error");
this.logger.error("Failed to upload image", e);
} }
}
ctx. public void downloadImage(Context ctx) {
status(HttpStatus.OK). try {
contentType(ContentType.IMAGE_PNG). String id = ctx.pathParam("id");
result(png); byte[] png = storage.get(id);
if (png == null) {
ctx
.status(HttpStatus.NOT_FOUND)
.result("Image not found");
return;
}
ctx.
status(HttpStatus.OK).
contentType(ContentType.IMAGE_PNG).
result(png);
} catch (Throwable e) {
ctx
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.result("Internal Server Error");
this.logger.error("Failed to download image", e);
}
} }
} }

View file

@ -17,15 +17,17 @@ public class Main {
new MemoryStorage(), new MemoryStorage(),
Duration.ofSeconds(5), Duration.ofSeconds(5),
Duration.ofMinutes(2), Duration.ofMinutes(2),
16 * 1024 * 1024, // 16 MiB
3000 3000
); );
} }
private static void run(Logger logger, Storage storage, Duration clearTime, TemporalAmount expireTime, int port) { private static void run(Logger logger, Storage storage, Duration clearTime, TemporalAmount expireTime, int maxImageSize, int port) {
ImageHandler handler = new ImageHandler( ImageHandler handler = new ImageHandler(
logger, logger,
storage, storage,
expireTime expireTime,
maxImageSize
); );
Thread.ofVirtual().start(() -> { Thread.ofVirtual().start(() -> {