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
WIP
1. Install a JDK: Ensure that you have Java 21 installed.
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'
version = '0.0.1'
version = project.version
repositories {
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'
version = '0.0.1'
version = project.version
base {
archivesName = "pixelchat-fabric-${project.minecraft_version}"

View file

@ -1,5 +1,6 @@
{
"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"
}

View file

@ -6,9 +6,9 @@ import java.util.UUID;
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
);
this.minecraft.send(() -> bossBars.add(bossBar.getUuid(), bossBar));
this.minecraft.send(() -> bossBars.pixelchat$add(bossBar.getUuid(), bossBar));
String url = null;
Throwable error = null;
for (String host : this.hosts) {
try {
url = this.upload(host, path);
error = null;
break;
} catch (Throwable e) {
error = e;
PixelChat.LOGGER.warn("Failed to upload", e);
}
}
// IntelliJ complains otherwise
String finalUrl = url;
Throwable finalError = error;
this.minecraft.send(() -> {
if (finalUrl == null) {
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.setColor(BossBar.Color.RED);
return;
@ -84,7 +95,7 @@ public class ImageUploads {
} 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 {
@ -99,7 +110,8 @@ public class ImageUploads {
HttpResponse.BodyHandlers.ofString()
);
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();

View file

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

View file

@ -16,6 +16,10 @@ public abstract class ChatScreenMixin extends Screen {
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
public void onFilesDropped(List<Path> paths) {
for (Path path : paths) {

View file

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

View file

@ -6,10 +6,10 @@ import io.javalin.http.Context;
import io.javalin.http.HttpStatus;
import org.apache.commons.imaging.ImageFormats;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.ImagingException;
import org.slf4j.Logger;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.time.temporal.TemporalAmount;
@ -19,34 +19,66 @@ public class ImageHandler {
private final Logger logger;
private final Storage storage;
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.storage = storage;
this.expireTime = expireTime;
this.maxImageSize = maxImageSize;
}
public void uploadImage(Context ctx) throws IOException {
public void uploadImage(Context ctx) {
try {
InputStream body = ctx.bodyInputStream();
BufferedImage image = Imaging.getBufferedImage(body);
byte[] png = Imaging.writeImageToBytes(image, ImageFormats.PNG);
if (png.length > maxImageSize) {
ctx
.status(HttpStatus.CONTENT_TOO_LARGE)
.result("Image too large");
return;
}
Instant expiresAt = Instant.now().plus(this.expireTime);
String id = storage.put(png, expiresAt);
logger.info("Stored {} bytes as {}", png.length, id);
logger.info("Uploaded {} image bytes as {}", png.length, id);
ctx.
status(HttpStatus.CREATED).
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);
}
}
public void downloadImage(Context ctx) throws IOException {
public void downloadImage(Context ctx) {
try {
String id = ctx.pathParam("id");
byte[] png = storage.get(id);
if (png == null) {
ctx.status(HttpStatus.NOT_FOUND);
ctx
.status(HttpStatus.NOT_FOUND)
.result("Image not found");
return;
}
@ -54,5 +86,12 @@ public class ImageHandler {
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(),
Duration.ofSeconds(5),
Duration.ofMinutes(2),
16 * 1024 * 1024, // 16 MiB
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(
logger,
storage,
expireTime
expireTime,
maxImageSize
);
Thread.ofVirtual().start(() -> {