Skip to content

Commit 6ebc5a5

Browse files
committed
Test execute command
1 parent e4eb247 commit 6ebc5a5

File tree

8 files changed

+164
-69
lines changed

8 files changed

+164
-69
lines changed

src/main/java/com/exaroton/api/server/Server.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.concurrent.CompletableFuture;
2020
import java.util.concurrent.Future;
2121

22+
@SuppressWarnings("unused")
2223
public final class Server implements Initializable {
2324

2425
/**
@@ -403,8 +404,8 @@ public Future<Server> waitForStatus(ServerStatus... statuses) {
403404
* @throws IOException connection errors
404405
*/
405406
public CompletableFuture<Void> executeCommand(String command) throws IOException {
406-
if (this.webSocket != null && this.webSocket.executeCommand(command)) {
407-
return CompletableFuture.completedFuture(null);
407+
if (this.webSocket != null) {
408+
return this.webSocket.executeCommand(command);
408409
}
409410
return client.request(new ExecuteCommandRequest(this.client, this.gson, this.id, command));
410411
}

src/main/java/com/exaroton/api/server/ServerStatus.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
import com.exaroton.api.BrandColor;
44

5-
import java.util.Optional;
5+
import java.util.*;
66

77
/**
88
* Server status
99
*/
10+
@SuppressWarnings("unused")
1011
public enum ServerStatus {
1112
OFFLINE(0, "Offline", BrandColor.DANGER),
1213
ONLINE(1, "Online", BrandColor.SUCCESS),
@@ -21,6 +22,18 @@ public enum ServerStatus {
2122
PREPARING(10, "Preparing", BrandColor.LOADING),
2223
;
2324

25+
/**
26+
* Group of statuses that are considered offline. The server files are stored in the storage system and
27+
* transferring to/from the host machine has not started yet.
28+
*/
29+
public static final Set<ServerStatus> GROUP_OFFLINE = Set.of(OFFLINE, PREPARING, CRASHED);
30+
31+
/**
32+
* Group of statuses that are considered stopping. It is expected that these will eventually lead to the server
33+
* being offline or crashed.
34+
*/
35+
public static final Set<ServerStatus> GROUP_STOPPING = Set.of(SAVING, STOPPING);
36+
2437
private final int value;
2538
private final String name;
2639
private final BrandColor color;

src/main/java/com/exaroton/api/ws/WebSocketConnection.java

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
import java.net.http.HttpClient;
2121
import java.net.http.WebSocket;
2222
import java.util.*;
23-
import java.util.concurrent.CompletableFuture;
24-
import java.util.concurrent.CompletionStage;
25-
import java.util.concurrent.Future;
23+
import java.util.concurrent.*;
2624

2725
public final class WebSocketConnection implements WebSocket.Listener {
2826
/**
@@ -50,6 +48,8 @@ public final class WebSocketConnection implements WebSocket.Listener {
5048
*/
5149
private final ArrayList<String> messages = new ArrayList<>();
5250

51+
private CompletableFuture<Void> messageQueueCleared = new CompletableFuture<>();
52+
5353
/**
5454
* is the connection ready
5555
*/
@@ -108,10 +108,12 @@ public void unsubscribe(@NotNull StreamType type) {
108108
return;
109109
}
110110

111-
Stream<?> stream = this.streams.get(type.getStreamClass());
112-
if (stream != null) {
113-
stream.stop();
114-
this.streams.remove(type.getStreamClass());
111+
synchronized (streams) {
112+
Stream<?> stream = this.streams.get(type.getStreamClass());
113+
if (stream != null) {
114+
stream.stop();
115+
this.streams.remove(type.getStreamClass());
116+
}
115117
}
116118
}
117119

@@ -121,14 +123,8 @@ public void unsubscribe(@NotNull StreamType type) {
121123
* @param command minecraft command
122124
* @return was the command executed
123125
*/
124-
public boolean executeCommand(String command) {
125-
ConsoleStream stream = this.getStream(ConsoleStream.class);
126-
if (stream != null) {
127-
stream.executeCommand(command);
128-
return true;
129-
}
130-
131-
return false;
126+
public CompletableFuture<Void> executeCommand(String command) {
127+
return this.getOrCreateStream(ConsoleStream.class).executeCommand(command);
132128
}
133129

134130
/**
@@ -191,9 +187,24 @@ public Future<Server> waitForStatus(Set<ServerStatus> statuses) {
191187
return new WaitForStatusSubscriber(statuses, this.getStream(ServerStatusStream.class));
192188
}
193189

194-
private <T extends Stream<?>> T getStream(Class<T> c) {
195-
@SuppressWarnings("unchecked") T stream = (T) this.streams.get(c);
196-
return stream;
190+
private <T extends Stream<?>> @NotNull T getOrCreateStream(Class<T> clazz) {
191+
synchronized (streams) {
192+
@SuppressWarnings("unchecked") T stream = (T) this.streams.computeIfAbsent(clazz, this::createAndStartStream);
193+
return stream;
194+
}
195+
}
196+
197+
private <T extends Stream<?>> @Nullable T getStream(Class<T> clazz) {
198+
synchronized (streams) {
199+
@SuppressWarnings("unchecked") T stream = (T) this.streams.get(clazz);
200+
return stream;
201+
}
202+
}
203+
204+
private @NotNull Stream<?> createAndStartStream(Class<? extends Stream<?>> clazz) {
205+
var created = StreamType.get(clazz).construct(this, gson);
206+
created.start();
207+
return created;
197208
}
198209

199210
private void connect() {
@@ -214,11 +225,7 @@ private void connect() {
214225
*/
215226
@ApiStatus.Internal
216227
public <T> void addStreamSubscriber(Class<? extends Stream<T>> c, T subscriber) {
217-
if (!this.streams.containsKey(c)) {
218-
this.streams.put(c, StreamType.get(c).construct(this, gson));
219-
}
220-
221-
getStream(c).addSubscriber(subscriber);
228+
getOrCreateStream(c).addSubscriber(subscriber);
222229
}
223230

224231
/**
@@ -229,25 +236,28 @@ public <T> void addStreamSubscriber(Class<? extends Stream<T>> c, T subscriber)
229236
*/
230237
@ApiStatus.Internal
231238
public <T> void removeStreamSubscriber(Class<? extends Stream<T>> c, T subscriber) {
232-
if (!this.streams.containsKey(c)) {
239+
var stream = getStream(c);
240+
if (stream == null) {
233241
return;
234242
}
235243

236-
getStream(c).removeSubscriber(subscriber);
244+
stream.removeSubscriber(subscriber);
237245
unsubscribeFromEmptyStreams();
238246
}
239247

240248
@ApiStatus.Internal
241249
public void unsubscribeFromEmptyStreams() {
242-
for (Stream<?> stream : streams.values()) {
243-
if (stream.hasNoSubscribers()) {
244-
this.unsubscribe(stream.getType());
250+
synchronized (streams) {
251+
for (Stream<?> stream : new ArrayList<>(streams.values())) {
252+
if (stream.hasNoSubscribers()) {
253+
this.unsubscribe(stream.getType());
254+
}
245255
}
246-
}
247256

248-
// server status stream is always active
249-
if (streams.size() == 1 && getStream(ServerStatusStream.class).hasNoSubscribers()) {
250-
this.server.unsubscribe();
257+
// server status stream is always active
258+
if (streams.size() == 1 && getOrCreateStream(ServerStatusStream.class).hasNoSubscribers()) {
259+
this.server.unsubscribe();
260+
}
251261
}
252262
}
253263

@@ -282,11 +292,13 @@ public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean
282292
webSocket.sendText(x, true);
283293
}
284294
this.messages.clear();
295+
this.messageQueueCleared.complete(null);
296+
this.messageQueueCleared = new CompletableFuture<>();
285297
break;
286298

287299
default:
288300
final StreamType name = StreamType.get(message.get("stream").getAsString());
289-
final Stream<?> stream = streams.get(name.getStreamClass());
301+
final Stream<?> stream = getStream(name.getStreamClass());
290302
if (stream != null) {
291303
stream.onMessage(type, message);
292304
}
@@ -298,8 +310,10 @@ public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean
298310
@ApiStatus.Internal
299311
public void onStatusChange() {
300312
// start/stop streams based on status
301-
for (Stream<?> s : streams.values()) {
302-
s.onStatusChange();
313+
synchronized (streams) {
314+
for (Stream<?> s : streams.values()) {
315+
s.onStatusChange();
316+
}
303317
}
304318
}
305319

@@ -308,8 +322,10 @@ public void onStatusChange() {
308322
public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
309323
logger.info("Websocket connection to {} closed: {} {}", uri, statusCode, reason);
310324

311-
for (Stream<?> stream : streams.values()) {
312-
stream.onDisconnected();
325+
synchronized (streams) {
326+
for (Stream<?> stream : streams.values()) {
327+
stream.onDisconnected();
328+
}
313329
}
314330

315331
if (this.shouldAutoReconnect()) {
@@ -337,12 +353,12 @@ public void onError(WebSocket webSocket, Throwable error) {
337353
* @param data web socket message
338354
*/
339355
@ApiStatus.Internal
340-
public void sendWhenReady(String data) {
356+
public CompletableFuture<Void> sendWhenReady(String data) {
341357
if (this.client == null || !this.ready) {
342358
this.messages.add(data);
343-
return;
359+
return messageQueueCleared;
344360
}
345-
this.client.sendText(data, true);
361+
return this.client.sendText(data, true).thenAccept(x -> {});
346362
}
347363

348364
/**

src/main/java/com/exaroton/api/ws/stream/ConsoleStream.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import org.jetbrains.annotations.ApiStatus;
99
import org.jetbrains.annotations.NotNull;
1010

11+
import java.util.concurrent.CompletableFuture;
12+
1113
@ApiStatus.Internal
1214
public final class ConsoleStream extends Stream<ConsoleSubscriber> {
1315
public ConsoleStream(@NotNull WebSocketConnection ws, @NotNull Gson gson) {
@@ -16,10 +18,12 @@ public ConsoleStream(@NotNull WebSocketConnection ws, @NotNull Gson gson) {
1618

1719
/**
1820
* execute a command using the websocket
21+
*
1922
* @param command minecraft command
23+
* @return a future that completes when the websocket message was sent
2024
*/
21-
public void executeCommand(String command) {
22-
this.send("command", command);
25+
public CompletableFuture<Void> executeCommand(String command) {
26+
return this.send("command", command);
2327
}
2428

2529
@Override

src/main/java/com/exaroton/api/ws/stream/Stream.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,23 @@ public boolean hasNoSubscribers() {
7878

7979
/**
8080
* send stream data through the websocket
81+
*
8182
* @param type message type
83+
* @return future that completes when the message is actually sent
8284
*/
83-
public void send(String type) {
84-
ws.sendWhenReady(gson.toJson(new StreamData<>(this.getType().getName(), type)));
85+
public CompletableFuture<Void> send(String type) {
86+
return send(type, null);
8587
}
8688

8789
/**
8890
* send stream data through the websocket
91+
*
8992
* @param type message type
9093
* @param data message data
94+
* @return future that completes when the message is actually sent
9195
*/
92-
public void send(String type, String data) {
93-
ws.sendWhenReady(gson.toJson(new StreamData<>(this.getType().getName(), type, data)));
96+
public CompletableFuture<Void> send(String type, String data) {
97+
return ws.sendWhenReady(gson.toJson(new StreamData<>(this.getType().getName(), type, data)));
9498
}
9599

96100
/**
@@ -134,10 +138,11 @@ public CompletableFuture<Void> tryToStart() {
134138
return CompletableFuture.completedFuture(null);
135139
}
136140

137-
return shouldBeStarted().thenAccept(shouldBeStarted -> {
141+
return shouldBeStarted().thenCompose(shouldBeStarted -> {
138142
if (shouldBeStarted) {
139-
this.send("start");
143+
return this.send("start");
140144
}
145+
return CompletableFuture.completedFuture(null);
141146
});
142147
}
143148

@@ -154,10 +159,11 @@ public CompletableFuture<Void> tryToStop() {
154159
return CompletableFuture.completedFuture(null);
155160
}
156161

157-
return this.shouldBeStarted().thenAccept(shouldBeStarted -> {
162+
return this.shouldBeStarted().thenCompose(shouldBeStarted -> {
158163
if (!shouldBeStarted) {
159-
this.send("stop");
164+
return this.send("stop");
160165
}
166+
return CompletableFuture.completedFuture(null);
161167
});
162168
}
163169

src/test/java/APIClientTest.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.junit.jupiter.api.BeforeEach;
77

88
import java.io.IOException;
9+
import java.util.HashSet;
910
import java.util.concurrent.ExecutionException;
1011
import java.util.concurrent.TimeUnit;
1112
import java.util.concurrent.TimeoutException;
@@ -21,7 +22,7 @@ void setUp() {
2122
server = client.getServer(TEST_SERVER_ID);
2223

2324
try {
24-
Thread.sleep(100);
25+
Thread.sleep(200);
2526
} catch (InterruptedException e) {
2627
throw new RuntimeException(e);
2728
}
@@ -32,13 +33,17 @@ void tearDown() throws IOException, ExecutionException, InterruptedException, Ti
3233
server.fetch().join();
3334

3435
if (server.hasStatus(ServerStatus.RESTARTING, ServerStatus.LOADING, ServerStatus.PREPARING)) {
35-
server.waitForStatus(ServerStatus.STARTING, ServerStatus.ONLINE, ServerStatus.OFFLINE, ServerStatus.CRASHED)
36+
var stoppableOrStopped = new HashSet<>(ServerStatus.GROUP_OFFLINE);
37+
stoppableOrStopped.add(ServerStatus.STARTING);
38+
stoppableOrStopped.add(ServerStatus.ONLINE);
39+
40+
server.waitForStatus(stoppableOrStopped)
3641
.get(1, TimeUnit.MINUTES);
3742
}
3843

39-
if (server.hasStatus(ServerStatus.ONLINE, ServerStatus.STARTING)) {
44+
if (!server.hasStatus(ServerStatus.GROUP_OFFLINE)) {
4045
server.stop().join();
41-
server.waitForStatus(ServerStatus.OFFLINE, ServerStatus.CRASHED).get(1, TimeUnit.MINUTES);
46+
server.waitForStatus(ServerStatus.GROUP_OFFLINE).get(1, TimeUnit.MINUTES);
4247
}
4348

4449
server.unsubscribe();

src/test/java/FileTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class FileTest extends APIClientTest {
1818
@Test
1919
void getFile() throws IOException {
2020
ServerFile whitelist = server.getFile("whitelist.json");
21-
whitelist.putContent("[{\"name\":\"JulianVennen\", \"uuid\": \"abcd9e56-5ac2-490c-8bc9-6c1cad18f506\"}]");
21+
whitelist.putContent("[{\"name\":\"JulianVennen\", \"uuid\": \"abcd9e56-5ac2-490c-8bc9-6c1cad18f506\"}]").join();
2222
assertNotNull(whitelist);
2323
assertNotNull(whitelist.fetch().join());
2424
assertFalse(whitelist.isConfigFile());

0 commit comments

Comments
 (0)