Skip to content

Commit d440199

Browse files
committed
begin live inventory
1 parent d74b07f commit d440199

8 files changed

Lines changed: 986 additions & 29 deletions

File tree

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ repositories {
2121
}
2222

2323
maven { url = uri("https://maven.enginehub.org/repo/") }
24+
25+
maven { url = uri("https://repo.codemc.io/repository/maven-public/") }
2426
}
2527

2628
dependencies {
@@ -33,6 +35,7 @@ dependencies {
3335
plexLibrary("org.eclipse.jetty:jetty-server:12.1.9")
3436
plexLibrary("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.9")
3537
plexLibrary("org.eclipse.jetty:jetty-proxy:12.1.9")
38+
plexLibrary("de.tr7zw:item-nbt-api:2.15.7")
3639
implementation(platform("com.intellectualsites.bom:bom-newest:1.56")) // Ref: https://github.com/IntellectualSites/bom
3740
compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core")
3841
implementation("commons-io:commons-io:2.22.0")

src/main/java/dev/plex/HTTPDModule.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import dev.plex.ratelimit.RateLimitFilter;
99
import dev.plex.request.AbstractServlet;
1010
import dev.plex.request.PlayerActionServlet;
11+
import dev.plex.request.PlayerInventoryStreamServlet;
1112
import dev.plex.request.PlayersStreamServlet;
1213
import dev.plex.request.SchematicUploadServlet;
1314
import dev.plex.request.StaffPlayersStreamServlet;
@@ -99,6 +100,7 @@ public void enable()
99100

100101
StatsBroadcaster.get().start();
101102
PlayersBroadcaster.get().start();
103+
PlayerInventoryBroadcaster.get().start();
102104

103105
new IndefBansEndpoint();
104106
new IndexEndpoint();
@@ -118,6 +120,7 @@ public void enable()
118120
HTTPDModule.context.addServlet(PlayersStreamServlet.class, "/api/players/stream");
119121
HTTPDModule.context.addServlet(StaffPlayersStreamServlet.class, "/api/players/stream/staff");
120122
HTTPDModule.context.addServlet(PlayerActionServlet.class, "/api/admin/action");
123+
HTTPDModule.context.addServlet(PlayerInventoryStreamServlet.class, "/api/player/inventory/stream");
121124

122125
ServletHolder uploadHolder = HTTPDModule.context.addServlet(SchematicUploadServlet.class, "/api/schematics/uploading");
123126

@@ -167,6 +170,14 @@ public void disable()
167170
t.printStackTrace();
168171
}
169172
try
173+
{
174+
PlayerInventoryBroadcaster.get().shutdown();
175+
}
176+
catch (Throwable t)
177+
{
178+
t.printStackTrace();
179+
}
180+
try
170181
{
171182
atomicServer.get().stop();
172183
atomicServer.get().destroy();

src/main/java/dev/plex/request/AbstractServlet.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,16 +174,22 @@ public static String readFile(InputStream filename)
174174
{
175175
String base = HTTPDModule.template;
176176
String page = readFileReal(filename);
177-
String[] info = page.split("\n", 3);
178-
base = base.replace("${TITLE}", info[0]);
179-
base = base.replace("${ACTIVE_" + info[1] + "}", "active");
177+
String[] info = page.split("\\r?\\n", 3);
178+
String title = info.length > 0 ? info[0] : "";
179+
String activeKey = info.length > 1 ? info[1] : "";
180+
String content = info.length > 2 ? info[2] : "";
181+
base = base.replace("${TITLE}", title);
182+
if (!activeKey.isEmpty())
183+
{
184+
base = base.replace("${ACTIVE_" + activeKey + "}", "active");
185+
}
180186
base = base.replace("${ACTIVE_HOME}", "");
181187
base = base.replace("${ACTIVE_PLAYERS}", "");
182188
base = base.replace("${ACTIVE_INDEFBANS}", "");
183189
base = base.replace("${ACTIVE_COMMANDS}", "");
184190
base = base.replace("${ACTIVE_PUNISHMENTS}", "");
185191
base = base.replace("${ACTIVE_SCHEMATICS}", "");
186-
base = base.replace("${CONTENT}", info[2]);
192+
base = base.replace("${CONTENT}", content);
187193
return base;
188194
}
189195

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package dev.plex.request;
2+
3+
import dev.plex.logging.Log;
4+
import dev.plex.request.impl.PlayerInventoryBroadcaster;
5+
import jakarta.servlet.AsyncContext;
6+
import jakarta.servlet.AsyncEvent;
7+
import jakarta.servlet.AsyncListener;
8+
import jakarta.servlet.ServletException;
9+
import jakarta.servlet.http.HttpServlet;
10+
import jakarta.servlet.http.HttpServletRequest;
11+
import jakarta.servlet.http.HttpServletResponse;
12+
13+
import java.io.IOException;
14+
import java.io.PrintWriter;
15+
import java.util.UUID;
16+
17+
public class PlayerInventoryStreamServlet extends HttpServlet
18+
{
19+
@Override
20+
protected void doGet(HttpServletRequest request, HttpServletResponse response)
21+
throws ServletException, IOException
22+
{
23+
if (AbstractServlet.currentStaff(request) == null)
24+
{
25+
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
26+
return;
27+
}
28+
29+
String uuidStr = request.getParameter("uuid");
30+
if (uuidStr == null)
31+
{
32+
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
33+
return;
34+
}
35+
final UUID uuid;
36+
try
37+
{
38+
uuid = UUID.fromString(uuidStr);
39+
}
40+
catch (IllegalArgumentException e)
41+
{
42+
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
43+
return;
44+
}
45+
46+
String ipAddress = request.getRemoteAddr();
47+
if ("127.0.0.1".equals(ipAddress))
48+
{
49+
String forwarded = request.getHeader("X-FORWARDED-FOR");
50+
if (forwarded != null) ipAddress = forwarded;
51+
}
52+
Log.log(ipAddress + " opened inventory stream for " + uuid);
53+
54+
PlayerInventoryBroadcaster broadcaster = PlayerInventoryBroadcaster.get();
55+
if (broadcaster.atCapacity())
56+
{
57+
response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
58+
response.setHeader("Retry-After", "30");
59+
return;
60+
}
61+
62+
response.setStatus(HttpServletResponse.SC_OK);
63+
response.setContentType("text/event-stream");
64+
response.setCharacterEncoding("UTF-8");
65+
response.setHeader("Cache-Control", "no-cache, no-transform");
66+
response.setHeader("Connection", "keep-alive");
67+
response.setHeader("X-Accel-Buffering", "no");
68+
69+
final AsyncContext ctx = request.startAsync();
70+
ctx.setTimeout(0L);
71+
ctx.addListener(new AsyncListener()
72+
{
73+
@Override public void onComplete(AsyncEvent event) { broadcaster.removeSubscriber(uuid, ctx); }
74+
@Override public void onTimeout(AsyncEvent event) { broadcaster.removeSubscriber(uuid, ctx); }
75+
@Override public void onError(AsyncEvent event) { broadcaster.removeSubscriber(uuid, ctx); }
76+
@Override public void onStartAsync(AsyncEvent event) {}
77+
});
78+
79+
PrintWriter writer;
80+
try
81+
{
82+
writer = response.getWriter();
83+
}
84+
catch (IOException e)
85+
{
86+
ctx.complete();
87+
return;
88+
}
89+
90+
if (!broadcaster.addSubscriber(uuid, ctx, writer))
91+
{
92+
response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
93+
ctx.complete();
94+
return;
95+
}
96+
97+
try
98+
{
99+
writer.write("retry: 5000\n\n");
100+
writer.write("data: ");
101+
writer.write(broadcaster.currentPayload(uuid));
102+
writer.write("\n\n");
103+
writer.flush();
104+
if (writer.checkError())
105+
{
106+
broadcaster.removeSubscriber(uuid, ctx);
107+
ctx.complete();
108+
}
109+
}
110+
catch (Throwable t)
111+
{
112+
broadcaster.removeSubscriber(uuid, ctx);
113+
try { ctx.complete(); } catch (Throwable ignored) {}
114+
}
115+
}
116+
}

src/main/java/dev/plex/request/impl/AssetsEndpoint.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
import java.io.IOException;
1010
import java.io.InputStream;
1111
import java.io.OutputStream;
12+
import java.util.regex.Pattern;
1213

1314
public class AssetsEndpoint extends AbstractServlet
1415
{
16+
private static final Pattern TEXTURE_PATH = Pattern.compile("(item|block)/[a-z0-9_]+\\.png");
17+
18+
1519
@GetMapping(endpoint = "/assets/dashboard.js")
1620
@MappingHeaders(headers = {"content-type;application/javascript; charset=utf-8", "cache-control;public, max-age=300"})
1721
public String dashboardJs(HttpServletRequest request, HttpServletResponse response)
@@ -41,6 +45,27 @@ public String plexLogo(HttpServletRequest request, HttpServletResponse response)
4145
return null;
4246
}
4347

48+
@GetMapping(endpoint = "/assets/textures/")
49+
@MappingHeaders(headers = {"content-type;image/png", "cache-control;public, max-age=86400"})
50+
public String texture(HttpServletRequest request, HttpServletResponse response)
51+
{
52+
String uri = request.getRequestURI();
53+
String prefix = "/assets/textures/";
54+
if (!uri.startsWith(prefix))
55+
{
56+
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
57+
return null;
58+
}
59+
String resourcePath = uri.substring(prefix.length());
60+
if (!TEXTURE_PATH.matcher(resourcePath).matches())
61+
{
62+
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
63+
return null;
64+
}
65+
serveResource("/httpd/assets/textures/" + resourcePath, response);
66+
return null;
67+
}
68+
4469
private static void serveResource(String classpathPath, HttpServletResponse response)
4570
{
4671
try (InputStream in = AssetsEndpoint.class.getResourceAsStream(classpathPath);

0 commit comments

Comments
 (0)