From ad3316ffdd408e2c95914924cb3aafa591019e4f Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 30 Nov 2023 17:43:52 +0100 Subject: [PATCH] Add config endpoints --- CHANGELOG.md | 4 +- README.md | 37 +++- build.gradle | 5 + .../java/com/exaroton/api/APIRequest.java | 4 +- .../java/com/exaroton/api/ExarotonClient.java | 15 ++ .../request/server/files/FileDataRequest.java | 17 +- .../api/request/server/files/FileRequest.java | 25 +++ .../server/files/GetConfigOptionsRequest.java | 26 +++ .../files/UpdateConfigOptionsRequest.java | 39 ++++ .../java/com/exaroton/api/server/Server.java | 2 +- .../com/exaroton/api/server/ServerFile.java | 10 +- .../api/server/config/ConfigOption.java | 35 ++++ .../config/ConfigOptionTypeAdapter.java | 181 ++++++++++++++++++ .../api/server/config/OptionType.java | 18 ++ .../api/server/config/ServerConfig.java | 69 +++++++ .../config/options/BooleanConfigOption.java | 23 +++ .../config/options/FloatConfigOption.java | 24 +++ .../config/options/IntegerConfigOption.java | 23 +++ .../options/MultiselectConfigOption.java | 24 +++ .../config/options/SelectConfigOption.java | 24 +++ .../config/options/StringConfigOption.java | 24 +++ .../com/exaroton/api/ws/WebSocketClient.java | 4 +- .../com/exaroton/api/ws/WebSocketManager.java | 21 +- .../com/exaroton/api/ws/stream/Stream.java | 4 +- .../java/ConfigOptionTypeAdapterTest.java | 139 ++++++++++++++ src/test/java/GetConfigTest.java | 29 +++ 26 files changed, 791 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/exaroton/api/request/server/files/FileRequest.java create mode 100644 src/main/java/com/exaroton/api/request/server/files/GetConfigOptionsRequest.java create mode 100644 src/main/java/com/exaroton/api/request/server/files/UpdateConfigOptionsRequest.java create mode 100644 src/main/java/com/exaroton/api/server/config/ConfigOption.java create mode 100644 src/main/java/com/exaroton/api/server/config/ConfigOptionTypeAdapter.java create mode 100644 src/main/java/com/exaroton/api/server/config/OptionType.java create mode 100644 src/main/java/com/exaroton/api/server/config/ServerConfig.java create mode 100644 src/main/java/com/exaroton/api/server/config/options/BooleanConfigOption.java create mode 100644 src/main/java/com/exaroton/api/server/config/options/FloatConfigOption.java create mode 100644 src/main/java/com/exaroton/api/server/config/options/IntegerConfigOption.java create mode 100644 src/main/java/com/exaroton/api/server/config/options/MultiselectConfigOption.java create mode 100644 src/main/java/com/exaroton/api/server/config/options/SelectConfigOption.java create mode 100644 src/main/java/com/exaroton/api/server/config/options/StringConfigOption.java create mode 100644 src/test/java/ConfigOptionTypeAdapterTest.java create mode 100644 src/test/java/GetConfigTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ed05ae..1520d76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1 @@ -- Add option to use own credits to Server.start() -- Use JSON body instead of form data -- Update dependencies \ No newline at end of file +- Add endpoints for the server config \ No newline at end of file diff --git a/README.md b/README.md index 1bc0ac7..b1bad56 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,42 @@ file.delete(); file.createAsDirectory(); ``` -### Websocket API +### Configs +Some files are special because they are parsed, validated and understood by the exaroton backend. +These files are called configs and can be managed like this: +```java +ExarotonClient client = new ExarotonClient("example-api-token"); + +Server server = client.getServer("tgkm731xO7GiHt76"); +ServerFile file = server.getFile("/server.properties"); +ServerConfig config = file.getConfig(); + +Map options = config.getOptions(); +for (ServerConfigOption option: options) { + System.out.println(option.getName() + ": " + option.getValue()); +} + +ConfigOption option = config.getOption("level-seed"); +``` + +There are several types of options which extend the ServerConfigOption class: +```java +for (ServerConfigOption option: options) { + if (option.getType() == OptionType.BOOLEAN) { + BooleanConfigOption booleanOption = (BooleanConfigOption) option; + System.out.println(booleanOption.getName() + ": " + booleanOption.getValue()); + } +} +``` + +To save changes to a config, use the save() method: +```java +config.getOption("level-seed").setValue("example"); +config.save(); +``` + + +## Websocket API The websocket API allows a constant connection to our websocket service to receive events in real time without polling (e.g. trying to get the server status every few seconds). diff --git a/build.gradle b/build.gradle index 8a1f5c8..ee05883 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,7 @@ dependencies { implementation 'org.java-websocket:Java-WebSocket:1.5.3' implementation 'org.slf4j:slf4j-api:2.0.7' implementation 'org.slf4j:slf4j-jdk14:2.0.7' + implementation 'org.jetbrains:annotations:24.1.0' } test { @@ -31,6 +32,10 @@ test { java { withJavadocJar() withSourcesJar() + + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } } ext.isReleaseVersion = !version.endsWith("SNAPSHOT") diff --git a/src/main/java/com/exaroton/api/APIRequest.java b/src/main/java/com/exaroton/api/APIRequest.java index 660ee02..3bd5b18 100644 --- a/src/main/java/com/exaroton/api/APIRequest.java +++ b/src/main/java/com/exaroton/api/APIRequest.java @@ -62,7 +62,7 @@ public InputStream requestRaw() throws APIException { Object body = this.getBody(); InputStream inputStream = this.getInputStream(); if (body != null) { - inputStream = new ByteArrayInputStream((new Gson()).toJson(body).getBytes(StandardCharsets.UTF_8)); + inputStream = new ByteArrayInputStream(client.getGson().toJson(body).getBytes(StandardCharsets.UTF_8)); connection.setRequestProperty("Content-Type", "application/json"); } @@ -101,7 +101,7 @@ public String requestString() throws APIException { public APIResponse request() throws APIException { String json = this.requestString(); - APIResponse response = (new Gson()).fromJson(json, this.getType()); + APIResponse response = client.getGson().fromJson(json, this.getType()); if (!response.isSuccess()) throw new APIException(response.getError()); return response; diff --git a/src/main/java/com/exaroton/api/ExarotonClient.java b/src/main/java/com/exaroton/api/ExarotonClient.java index 6bb8235..f0d27f3 100644 --- a/src/main/java/com/exaroton/api/ExarotonClient.java +++ b/src/main/java/com/exaroton/api/ExarotonClient.java @@ -4,6 +4,10 @@ import com.exaroton.api.request.account.GetAccountRequest; import com.exaroton.api.request.server.GetServersRequest; import com.exaroton.api.server.Server; +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.ConfigOptionTypeAdapter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import java.io.BufferedReader; import java.io.IOException; @@ -16,6 +20,10 @@ import java.util.stream.Collectors; public class ExarotonClient { + /** + * Gson instance used for (de-)serialization + */ + private final Gson gson; /** * Request protocol @@ -47,6 +55,13 @@ public class ExarotonClient { */ public ExarotonClient(String apiToken) { this.apiToken = apiToken; + this.gson = new GsonBuilder() + .registerTypeAdapter(ConfigOption.class, new ConfigOptionTypeAdapter()) + .create(); + } + + public Gson getGson() { + return this.gson; } diff --git a/src/main/java/com/exaroton/api/request/server/files/FileDataRequest.java b/src/main/java/com/exaroton/api/request/server/files/FileDataRequest.java index 0857711..4705c78 100644 --- a/src/main/java/com/exaroton/api/request/server/files/FileDataRequest.java +++ b/src/main/java/com/exaroton/api/request/server/files/FileDataRequest.java @@ -8,14 +8,9 @@ import java.lang.reflect.Type; import java.util.HashMap; -public class FileDataRequest extends APIRequest { - protected final String serverId; - protected final String path; - +public class FileDataRequest extends FileRequest { public FileDataRequest(ExarotonClient client, String serverId, String path) { - super(client); - this.serverId = serverId; - this.path = path; + super(client, serverId, path); } @Override @@ -27,12 +22,4 @@ protected String getEndpoint() { protected Type getType() { return new TypeToken>(){}.getType(); } - - @Override - protected HashMap getData() { - HashMap map = super.getData(); - map.put("server", this.serverId); - map.put("path", this.path); - return map; - } } diff --git a/src/main/java/com/exaroton/api/request/server/files/FileRequest.java b/src/main/java/com/exaroton/api/request/server/files/FileRequest.java new file mode 100644 index 0000000..48b0ace --- /dev/null +++ b/src/main/java/com/exaroton/api/request/server/files/FileRequest.java @@ -0,0 +1,25 @@ +package com.exaroton.api.request.server.files; + +import com.exaroton.api.APIRequest; +import com.exaroton.api.ExarotonClient; + +import java.util.HashMap; + +public abstract class FileRequest extends APIRequest { + protected final String serverId; + protected final String path; + + public FileRequest(ExarotonClient client, String serverId, String path) { + super(client); + this.serverId = serverId; + this.path = path; + } + + @Override + protected HashMap getData() { + HashMap map = super.getData(); + map.put("server", this.serverId); + map.put("path", this.path); + return map; + } +} diff --git a/src/main/java/com/exaroton/api/request/server/files/GetConfigOptionsRequest.java b/src/main/java/com/exaroton/api/request/server/files/GetConfigOptionsRequest.java new file mode 100644 index 0000000..d8c7aae --- /dev/null +++ b/src/main/java/com/exaroton/api/request/server/files/GetConfigOptionsRequest.java @@ -0,0 +1,26 @@ +package com.exaroton.api.request.server.files; + +import com.exaroton.api.APIResponse; +import com.exaroton.api.ExarotonClient; +import com.exaroton.api.server.config.ConfigOption; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; + +public class GetConfigOptionsRequest extends FileRequest { + + public GetConfigOptionsRequest(ExarotonClient client, String serverId, String path) { + super(client, serverId, path); + } + + + @Override + protected String getEndpoint() { + return "servers/{server}/files/config/{path}"; + } + + @Override + protected Type getType() { + return new TypeToken>(){}.getType(); + } +} diff --git a/src/main/java/com/exaroton/api/request/server/files/UpdateConfigOptionsRequest.java b/src/main/java/com/exaroton/api/request/server/files/UpdateConfigOptionsRequest.java new file mode 100644 index 0000000..35b3727 --- /dev/null +++ b/src/main/java/com/exaroton/api/request/server/files/UpdateConfigOptionsRequest.java @@ -0,0 +1,39 @@ +package com.exaroton.api.request.server.files; + +import com.exaroton.api.APIResponse; +import com.exaroton.api.ExarotonClient; +import com.exaroton.api.server.config.ConfigOption; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +public class UpdateConfigOptionsRequest extends FileRequest { + private final Map options; + + public UpdateConfigOptionsRequest(ExarotonClient client, String serverId, String path, Map options) { + super(client, serverId, path); + this.options = options; + } + + @Override + protected String getEndpoint() { + return "servers/{server}/files/config/{path}"; + } + + @Override + protected Type getType() { + return new TypeToken>(){}.getType(); + } + + @Override + protected String getMethod() { + return "POST"; + } + + @Override + protected Object getBody() { + return client.getGson().toJson(this.options); + } +} diff --git a/src/main/java/com/exaroton/api/server/Server.java b/src/main/java/com/exaroton/api/server/Server.java index d9c31ab..8a43eea 100644 --- a/src/main/java/com/exaroton/api/server/Server.java +++ b/src/main/java/com/exaroton/api/server/Server.java @@ -382,7 +382,7 @@ public void setClient(ExarotonClient client) { public void subscribe() { String protocol = this.client.getProtocol().equals("https") ? "wss" : "ws"; String uri = protocol + "://" + this.client.getHost() + this.client.getBasePath() + "servers/" + this.id + "/websocket"; - this.webSocket = new WebSocketManager(uri, this.client.getApiToken(), this); + this.webSocket = new WebSocketManager(client, uri, this.client.getApiToken(), this); } /** diff --git a/src/main/java/com/exaroton/api/server/ServerFile.java b/src/main/java/com/exaroton/api/server/ServerFile.java index b8c0ee6..7067482 100644 --- a/src/main/java/com/exaroton/api/server/ServerFile.java +++ b/src/main/java/com/exaroton/api/server/ServerFile.java @@ -3,6 +3,7 @@ import com.exaroton.api.APIException; import com.exaroton.api.ExarotonClient; import com.exaroton.api.request.server.files.*; +import com.exaroton.api.server.config.ServerConfig; import java.io.IOException; import java.io.InputStream; @@ -140,6 +141,14 @@ public void createAsDirectory() throws APIException { new CreateDirectoryRequest(this.client, this.server.getId(), this.path).request(); } + /** + * get a ServerConfig object for this file + * @return server config object + */ + public ServerConfig getConfig() { + return new ServerConfig(this.client, this.server, this.path); + } + public String getPath() { return path; } @@ -188,7 +197,6 @@ public void setPath(String path) { this.path = path.replaceAll("^/+", ""); } - /** * update properties from fetched object * @param file file fetched from the API diff --git a/src/main/java/com/exaroton/api/server/config/ConfigOption.java b/src/main/java/com/exaroton/api/server/config/ConfigOption.java new file mode 100644 index 0000000..2a0e8d7 --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/ConfigOption.java @@ -0,0 +1,35 @@ +package com.exaroton.api.server.config; + +import org.jetbrains.annotations.Nullable; + +public abstract class ConfigOption { + private final String key; + private final String label; + private final OptionType type; + private final String[] options; + + protected ConfigOption(String key, String label, OptionType type, String[] options) { + this.key = key; + this.label = label; + this.type = type; + this.options = options; + } + + public String getKey() { + return key; + } + + public String getLabel() { + return label; + } + + public @Nullable String[] getOptions() { + return options; + } + + abstract public @Nullable Object getValue(); + + public OptionType getType() { + return type; + } +} diff --git a/src/main/java/com/exaroton/api/server/config/ConfigOptionTypeAdapter.java b/src/main/java/com/exaroton/api/server/config/ConfigOptionTypeAdapter.java new file mode 100644 index 0000000..ecc6b78 --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/ConfigOptionTypeAdapter.java @@ -0,0 +1,181 @@ +package com.exaroton.api.server.config; + +import com.exaroton.api.server.config.options.BooleanConfigOption; +import com.exaroton.api.server.config.options.FloatConfigOption; +import com.exaroton.api.server.config.options.IntegerConfigOption; +import com.exaroton.api.server.config.options.MultiselectConfigOption; +import com.exaroton.api.server.config.options.SelectConfigOption; +import com.exaroton.api.server.config.options.StringConfigOption; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.google.gson.stream.MalformedJsonException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ConfigOptionTypeAdapter extends TypeAdapter { + @Override + public void write(JsonWriter out, ConfigOption value) throws IOException { + out.beginObject(); + out.name("key").value(value.getKey()); + out.name("label").value(value.getLabel()); + out.name("type").value(value.getType().name().toLowerCase()); + out.name("options").beginArray(); + for (String option : value.getOptions()) { + out.value(option); + } + out.endArray(); + out.name("value"); + switch (value.getType()) { + case STRING: + case SELECT: + out.value((String) value.getValue()); + break; + case INTEGER: + out.value((Long) value.getValue()); + break; + case FLOAT: + out.value((Double) value.getValue()); + break; + case BOOLEAN: + out.value((Boolean) value.getValue()); + break; + case MULTISELECT: + Object optionValue = value.getValue(); + if (optionValue == null) { + out.nullValue(); + break; + } + + out.beginArray(); + for (String option : (String[]) optionValue) { + out.value(option); + } + out.endArray(); + break; + } + } + + @Override + public ConfigOption read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + + in.beginObject(); + String key = null; + String label = null; + OptionType type = null; + Object value = null; + String[] options = null; + while (in.hasNext()) { + switch (in.nextName()) { + case "key": + key = in.nextString(); + break; + case "label": + label = in.nextString(); + break; + case "type": + type = OptionType.valueOf(in.nextString().toUpperCase()); + break; + case "value": + switch (in.peek()) { + case BEGIN_ARRAY: + List list = new ArrayList<>(); + in.beginArray(); + while (in.hasNext()) { + list.add(in.nextString()); + } + in.endArray(); + value = list.toArray(new String[0]); + break; + case STRING: + value = in.nextString(); + break; + case NUMBER: + value = in.nextDouble(); + break; + case BOOLEAN: + value = in.nextBoolean(); + break; + case NULL: + in.nextNull(); + value = null; + break; + default: + throw new MalformedJsonException("Unexpected token " + in.peek() + " at " + in.getPath()); + } + break; + case "options": + List list = new ArrayList<>(); + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + break; + } + in.beginArray(); + while (in.hasNext()) { + list.add(in.nextString()); + } + in.endArray(); + options = list.toArray(new String[0]); + break; + default: + in.skipValue(); + } + } + + in.endObject(); + + if (type == null) { + throw new MalformedJsonException("Missing getType at " + in.getPath()); + } + + switch (type) { + case STRING: + if (value == null || value instanceof String) { + return new StringConfigOption(key, (String) value, label, options); + } + throw new MalformedJsonException("Expected string getValue at " + in.getPath()); + + case INTEGER: + if (value == null || value instanceof Double) { + Double doubleValue = (Double) value; + Long longValue = doubleValue == null ? null : doubleValue.longValue(); + return new IntegerConfigOption(key, longValue, label, options); + } + throw new MalformedJsonException("Expected integer getValue at " + in.getPath()); + + case FLOAT: + if (value == null || value instanceof Double) { + return new FloatConfigOption(key, (Double) value, label, options); + } + throw new MalformedJsonException("Expected float getValue at " + in.getPath()); + + case BOOLEAN: + if (value == null || value instanceof Boolean) { + return new BooleanConfigOption(key, (Boolean) value, label, options); + } + throw new MalformedJsonException("Expected boolean getValue at " + in.getPath()); + + case MULTISELECT: + if (value == null || value instanceof String[]) { + return new MultiselectConfigOption(key, (String[]) value, label, options); + } + throw new MalformedJsonException("Expected string array getValue at " + in.getPath()); + + case SELECT: + if (value == null || value instanceof String) { + return new SelectConfigOption(key, (String) value, label, options); + } + throw new MalformedJsonException("Expected string getValue at " + in.getPath()); + + default: + throw new MalformedJsonException("Unexpected getType " + type + " at " + in.getPath()); + } + } +} diff --git a/src/main/java/com/exaroton/api/server/config/OptionType.java b/src/main/java/com/exaroton/api/server/config/OptionType.java new file mode 100644 index 0000000..eb8232e --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/OptionType.java @@ -0,0 +1,18 @@ +package com.exaroton.api.server.config; + +import com.google.gson.annotations.SerializedName; + +public enum OptionType { + @SerializedName("string") + STRING, + @SerializedName("integer") + INTEGER, + @SerializedName("float") + FLOAT, + @SerializedName("boolean") + BOOLEAN, + @SerializedName("multiselect") + MULTISELECT, + @SerializedName("select") + SELECT, +} diff --git a/src/main/java/com/exaroton/api/server/config/ServerConfig.java b/src/main/java/com/exaroton/api/server/config/ServerConfig.java new file mode 100644 index 0000000..5b89e81 --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/ServerConfig.java @@ -0,0 +1,69 @@ +package com.exaroton.api.server.config; + +import com.exaroton.api.APIException; +import com.exaroton.api.ExarotonClient; +import com.exaroton.api.request.server.files.GetConfigOptionsRequest; +import com.exaroton.api.request.server.files.UpdateConfigOptionsRequest; +import com.exaroton.api.server.Server; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class ServerConfig { + protected ExarotonClient client; + protected Server server; + protected String path; + protected Map options = null; + + public ServerConfig(ExarotonClient client, Server server, String path) { + this.client = client; + this.server = server; + this.path = path; + } + + public Map getOptions() throws APIException { + return this.getOptions(false); + } + + public Map getOptions(boolean update) throws APIException { + if (this.options == null || update) { + this.fetchOptions(); + } + return this.options; + } + + private void fetchOptions() throws APIException { + setOptions(new GetConfigOptionsRequest(this.client, this.server.getId(), this.path) + .request() + .getData()); + } + + private void setOptions(ConfigOption[] options) { + this.options = new HashMap<>(); + for (ConfigOption option : options) { + this.options.put(option.getKey(), option); + } + } + + public @Nullable ConfigOption getOption(String key) throws APIException { + return this.getOption(key, false); + } + + public @Nullable ConfigOption getOption(String key, boolean update) throws APIException { + return this.getOptions(update).get(key); + } + + public void save() throws APIException { + Map options = new HashMap<>(); + for (ConfigOption option : this.options.values()) { + if (option.getValue() != null) { + options.put(option.getKey(), option.getValue()); + } + } + + setOptions(new UpdateConfigOptionsRequest(this.client, this.server.getId(), this.path, options) + .request() + .getData()); + } +} diff --git a/src/main/java/com/exaroton/api/server/config/options/BooleanConfigOption.java b/src/main/java/com/exaroton/api/server/config/options/BooleanConfigOption.java new file mode 100644 index 0000000..9173e42 --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/options/BooleanConfigOption.java @@ -0,0 +1,23 @@ +package com.exaroton.api.server.config.options; + +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.OptionType; +import org.jetbrains.annotations.Nullable; + +public class BooleanConfigOption extends ConfigOption { + private Boolean value; + public BooleanConfigOption(String key, Boolean value, String label, String[] options) { + super(key, label, OptionType.BOOLEAN, options); + this.value = value; + } + + public BooleanConfigOption setValue(Boolean value) { + this.value = value; + return this; + } + + @Override + public @Nullable Boolean getValue() { + return value; + } +} diff --git a/src/main/java/com/exaroton/api/server/config/options/FloatConfigOption.java b/src/main/java/com/exaroton/api/server/config/options/FloatConfigOption.java new file mode 100644 index 0000000..97f68ca --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/options/FloatConfigOption.java @@ -0,0 +1,24 @@ +package com.exaroton.api.server.config.options; + +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.OptionType; +import org.jetbrains.annotations.Nullable; + +public class FloatConfigOption extends ConfigOption { + private Double value; + + public FloatConfigOption(String key, Double value, String label,String[] options) { + super(key, label, OptionType.FLOAT, options); + this.value = value; + } + + public FloatConfigOption setValue(Double value) { + this.value = value; + return this; + } + + @Override + public @Nullable Double getValue() { + return value; + } +} diff --git a/src/main/java/com/exaroton/api/server/config/options/IntegerConfigOption.java b/src/main/java/com/exaroton/api/server/config/options/IntegerConfigOption.java new file mode 100644 index 0000000..8ea96b3 --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/options/IntegerConfigOption.java @@ -0,0 +1,23 @@ +package com.exaroton.api.server.config.options; + +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.OptionType; +import org.jetbrains.annotations.Nullable; + +public class IntegerConfigOption extends ConfigOption { + private final Long value; + + public IntegerConfigOption(String key, Long value, String label,String[] options) { + super(key, label, OptionType.INTEGER, options); + this.value = value; + } + + public IntegerConfigOption setValue(Long value) { + return new IntegerConfigOption(this.getKey(), value, this.getLabel(), this.getOptions()); + } + + @Override + public @Nullable Long getValue() { + return value; + } +} diff --git a/src/main/java/com/exaroton/api/server/config/options/MultiselectConfigOption.java b/src/main/java/com/exaroton/api/server/config/options/MultiselectConfigOption.java new file mode 100644 index 0000000..4b7d81e --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/options/MultiselectConfigOption.java @@ -0,0 +1,24 @@ +package com.exaroton.api.server.config.options; + +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.OptionType; +import org.jetbrains.annotations.Nullable; + +public class MultiselectConfigOption extends ConfigOption { + private String[] value; + + public MultiselectConfigOption(String key, String[] value, String label, String[] options) { + super(key, label, OptionType.MULTISELECT, options); + this.value = value; + } + + public MultiselectConfigOption setValue(String... value) { + this.value = value; + return this; + } + + @Override + public @Nullable String[] getValue() { + return value; + } +} diff --git a/src/main/java/com/exaroton/api/server/config/options/SelectConfigOption.java b/src/main/java/com/exaroton/api/server/config/options/SelectConfigOption.java new file mode 100644 index 0000000..344ddfa --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/options/SelectConfigOption.java @@ -0,0 +1,24 @@ +package com.exaroton.api.server.config.options; + +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.OptionType; +import org.jetbrains.annotations.Nullable; + +public class SelectConfigOption extends ConfigOption { + private String value; + + public SelectConfigOption(String key, String value, String label, String[] options) { + super(key, label, OptionType.SELECT, options); + this.value = value; + } + + public SelectConfigOption setValue(String value) { + this.value = value; + return this; + } + + @Override + public @Nullable String getValue() { + return value; + } +} diff --git a/src/main/java/com/exaroton/api/server/config/options/StringConfigOption.java b/src/main/java/com/exaroton/api/server/config/options/StringConfigOption.java new file mode 100644 index 0000000..889d960 --- /dev/null +++ b/src/main/java/com/exaroton/api/server/config/options/StringConfigOption.java @@ -0,0 +1,24 @@ +package com.exaroton.api.server.config.options; + +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.OptionType; +import org.jetbrains.annotations.Nullable; + +public class StringConfigOption extends ConfigOption { + private String value; + + public StringConfigOption(String key, String value, String label,String[] options) { + super(key, label, OptionType.STRING, options); + this.value = value; + } + + public StringConfigOption setValue(String value) { + this.value = value; + return this; + } + + @Override + public @Nullable String getValue() { + return value; + } +} diff --git a/src/main/java/com/exaroton/api/ws/WebSocketClient.java b/src/main/java/com/exaroton/api/ws/WebSocketClient.java index 4745044..3c108e9 100644 --- a/src/main/java/com/exaroton/api/ws/WebSocketClient.java +++ b/src/main/java/com/exaroton/api/ws/WebSocketClient.java @@ -1,5 +1,6 @@ package com.exaroton.api.ws; +import com.exaroton.api.ExarotonClient; import com.google.gson.Gson; import org.java_websocket.handshake.ServerHandshake; import org.slf4j.Logger; @@ -8,7 +9,6 @@ import java.net.URI; public class WebSocketClient extends org.java_websocket.client.WebSocketClient { - /** * logger */ @@ -36,7 +36,7 @@ public void onOpen(ServerHandshake handshakedata) { @Override public void onMessage(String message) { - WSMessage m = (new Gson()).fromJson(message, WSMessage.class); + WSMessage m = manager.getGson().fromJson(message, WSMessage.class); switch (m.getType()) { case "connected": diff --git a/src/main/java/com/exaroton/api/ws/WebSocketManager.java b/src/main/java/com/exaroton/api/ws/WebSocketManager.java index 15a7486..b18635a 100644 --- a/src/main/java/com/exaroton/api/ws/WebSocketManager.java +++ b/src/main/java/com/exaroton/api/ws/WebSocketManager.java @@ -1,5 +1,6 @@ package com.exaroton.api.ws; +import com.exaroton.api.ExarotonClient; import com.exaroton.api.server.Server; import com.exaroton.api.ws.data.*; import com.exaroton.api.ws.stream.*; @@ -11,6 +12,7 @@ import java.util.*; public class WebSocketManager { + private final ExarotonClient exaroton; private final WebSocketClient client; @@ -42,7 +44,8 @@ public class WebSocketManager { private DebugListener debugListener = null; - public WebSocketManager(String uri, String apiToken, Server server) { + public WebSocketManager(ExarotonClient exaroton, String uri, String apiToken, Server server) { + this.exaroton = exaroton; try { URI u = new URI(uri); this.client = new WebSocketClient(u, this); @@ -54,6 +57,10 @@ public WebSocketManager(String uri, String apiToken, Server server) { this.server = server; } + public Gson getGson() { + return exaroton.getGson(); + } + /** * handle websocket data * @param type message type @@ -77,7 +84,7 @@ public void handleData(String type, String message) { case "status": Server oldServer = new Server(server.getClient(), server.getId()) .setFromObject(server); - this.server.setFromObject((new Gson()).fromJson(message, ServerStatusStreamData.class).getData()); + this.server.setFromObject(exaroton.getGson().fromJson(message, ServerStatusStreamData.class).getData()); //start/stop streams based on status for (Stream s: streams.values()) { @@ -92,14 +99,14 @@ public void handleData(String type, String message) { case "line": if (stream == null) return; - String line = (new Gson()).fromJson(message, ConsoleStreamData.class).getData(); + String line = exaroton.getGson().fromJson(message, ConsoleStreamData.class).getData(); for (Object subscriber: stream.subscribers) { ((ConsoleSubscriber) subscriber).line(line); } break; case "heap": if (stream == null) return; - HeapUsage usage = (new Gson()).fromJson(message, HeapStreamData.class).getData(); + HeapUsage usage = exaroton.getGson().fromJson(message, HeapStreamData.class).getData(); for (Object subscriber : stream.subscribers) { ((HeapSubscriber) subscriber).heap(usage); } @@ -107,7 +114,7 @@ public void handleData(String type, String message) { case "stats": if (stream == null) return; - StatsData stats = (new Gson()).fromJson(message, StatsStreamData.class).getData(); + StatsData stats = exaroton.getGson().fromJson(message, StatsStreamData.class).getData(); for (Object subscriber : stream.subscribers) { ((StatsSubscriber) subscriber).stats(stats); } @@ -115,7 +122,7 @@ public void handleData(String type, String message) { case "tick": if (stream == null) return; - TickData tick = (new Gson()).fromJson(message, TickStreamData.class).getData(); + TickData tick = exaroton.getGson().fromJson(message, TickStreamData.class).getData(); for (Object subscriber: stream.subscribers) { ((TickSubscriber) subscriber).tick(tick); } @@ -340,6 +347,4 @@ void sendDebug(String message) { void onError(String error, Throwable throwable) { this.errorListener.onError(error, throwable); } - - } diff --git a/src/main/java/com/exaroton/api/ws/stream/Stream.java b/src/main/java/com/exaroton/api/ws/stream/Stream.java index 14fcc5a..aee563f 100644 --- a/src/main/java/com/exaroton/api/ws/stream/Stream.java +++ b/src/main/java/com/exaroton/api/ws/stream/Stream.java @@ -31,7 +31,7 @@ public Stream(WebSocketManager ws) { * @param type message type */ public void send(String type) { - ws.sendWhenReady((new Gson()).toJson(new StreamData<>(this.getName(), type))); + ws.sendWhenReady(ws.getGson().toJson(new StreamData<>(this.getName(), type))); } /** @@ -45,7 +45,7 @@ public void send(String type) { * @param data message data */ public void send(String type, String data) { - ws.sendWhenReady((new Gson()).toJson(new StreamData<>(this.getName(), type, data))); + ws.sendWhenReady(ws.getGson().toJson(new StreamData<>(this.getName(), type, data))); } /** diff --git a/src/test/java/ConfigOptionTypeAdapterTest.java b/src/test/java/ConfigOptionTypeAdapterTest.java new file mode 100644 index 0000000..4f656ce --- /dev/null +++ b/src/test/java/ConfigOptionTypeAdapterTest.java @@ -0,0 +1,139 @@ +import com.exaroton.api.server.config.options.BooleanConfigOption; +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.ConfigOptionTypeAdapter; +import com.exaroton.api.server.config.options.FloatConfigOption; +import com.exaroton.api.server.config.options.IntegerConfigOption; +import com.exaroton.api.server.config.options.MultiselectConfigOption; +import com.exaroton.api.server.config.OptionType; +import com.exaroton.api.server.config.options.StringConfigOption; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ConfigOptionTypeAdapterTest { + private final Gson gson = new GsonBuilder() + .registerTypeAdapter(ConfigOption.class, new ConfigOptionTypeAdapter()) + + .create(); + + @Test + public void testDeserializeBooleanOption() { + String json = "{\"key\":\"spawn-monsters\",\"label\":\"Spawn Monsters\",\"type\":\"boolean\",\"value\":true,\"options\": null}"; + ConfigOption option = gson.fromJson(json, ConfigOption.class); + Assertions.assertNotNull(option); + Assertions.assertEquals("spawn-monsters", option.getKey()); + Assertions.assertEquals("Spawn Monsters", option.getLabel()); + Assertions.assertEquals(OptionType.BOOLEAN, option.getType()); + Assertions.assertEquals(true, option.getValue()); + Assertions.assertNull(option.getOptions()); + } + + @Test + public void testSerializeBooleanOption() { + BooleanConfigOption option = new BooleanConfigOption("spawn-monsters", true, "Spawn Monsters", null); + String json = gson.toJson(option); + Assertions.assertNotNull(json); + Assertions.assertEquals("{\"value\":true,\"key\":\"spawn-monsters\",\"label\":\"Spawn Monsters\",\"type\":\"boolean\"}", json); + } + + @Test + public void testDeserializeFloatOption() { + String json = "{\"key\":\"view-distance\",\"label\":\"View Distance\",\"type\":\"float\",\"value\":10.0,\"options\": null}"; + ConfigOption option = gson.fromJson(json, ConfigOption.class); + Assertions.assertNotNull(option); + Assertions.assertEquals("view-distance", option.getKey()); + Assertions.assertEquals("View Distance", option.getLabel()); + Assertions.assertEquals(OptionType.FLOAT, option.getType()); + Assertions.assertEquals(10.0, option.getValue()); + Assertions.assertNull(option.getOptions()); + } + + @Test + public void testSerializeFloatOption() { + ConfigOption option = new FloatConfigOption("view-distance", 10.0, "View Distance", null); + String json = gson.toJson(option); + Assertions.assertNotNull(json); + Assertions.assertEquals("{\"value\":10.0,\"key\":\"view-distance\",\"label\":\"View Distance\",\"type\":\"float\"}", json); + } + + @Test + public void testDeserializeIntegerOption() { + String json = "{\"key\":\"max-players\",\"label\":\"Max Players\",\"type\":\"integer\",\"value\":10,\"options\": null}"; + ConfigOption option = gson.fromJson(json, ConfigOption.class); + Assertions.assertNotNull(option); + Assertions.assertEquals("max-players", option.getKey()); + Assertions.assertEquals("Max Players", option.getLabel()); + Assertions.assertEquals(OptionType.INTEGER, option.getType()); + Assertions.assertEquals(10L, option.getValue()); + Assertions.assertNull(option.getOptions()); + } + + @Test + public void testSerializeIntegerOption() { + ConfigOption option = new IntegerConfigOption("max-players", 10L, "Max Players", null); + String json = gson.toJson(option); + Assertions.assertNotNull(json); + Assertions.assertEquals("{\"value\":10,\"key\":\"max-players\",\"label\":\"Max Players\",\"type\":\"integer\"}", json); + } + + @Test + public void testDeserializeStringOption() { + String json = "{\"key\":\"level-name\",\"label\":\"Level Name\",\"type\":\"string\",\"value\":\"world\",\"options\": null}"; + ConfigOption option = gson.fromJson(json, ConfigOption.class); + Assertions.assertNotNull(option); + Assertions.assertEquals("level-name", option.getKey()); + Assertions.assertEquals("Level Name", option.getLabel()); + Assertions.assertEquals(OptionType.STRING, option.getType()); + Assertions.assertEquals("world", option.getValue()); + Assertions.assertNull(option.getOptions()); + } + + @Test + public void testSerializeStringOption() { + ConfigOption option = new StringConfigOption("level-name", "world", "Level Name", null); + String json = gson.toJson(option); + Assertions.assertNotNull(json); + Assertions.assertEquals("{\"value\":\"world\",\"key\":\"level-name\",\"label\":\"Level Name\",\"type\":\"string\"}", json); + } + + @Test + public void testDeserializeMultiselectOption() { + String json = "{\"key\":\"enabled-datapacks\",\"label\":\"Enabled Datapacks\",\"type\":\"multiselect\",\"value\":[\"vanilla\",\"experiment-copper-golem\"],\"options\":[\"vanilla\",\"experiment-copper-golem\",\"experiment-penwing\",\"experiment-crab\"]}"; + ConfigOption option = gson.fromJson(json, ConfigOption.class); + Assertions.assertNotNull(option); + Assertions.assertEquals("enabled-datapacks", option.getKey()); + Assertions.assertEquals("Enabled Datapacks", option.getLabel()); + Assertions.assertEquals(OptionType.MULTISELECT, option.getType()); + Assertions.assertArrayEquals(new String[]{"vanilla", "experiment-copper-golem"}, (String[]) option.getValue()); + Assertions.assertArrayEquals(new String[]{"vanilla","experiment-copper-golem","experiment-penwing","experiment-crab"}, option.getOptions()); + } + + @Test + public void testSerializeMultiselectOption() { + ConfigOption option = new MultiselectConfigOption("enabled-datapacks", new String[]{"vanilla", "experiment-copper-golem"}, "Enabled Datapacks", new String[]{"vanilla","experiment-copper-golem","experiment-penwing","experiment-crab"}); + String json = gson.toJson(option); + Assertions.assertNotNull(json); + Assertions.assertEquals("{\"value\":[\"vanilla\",\"experiment-copper-golem\"],\"key\":\"enabled-datapacks\",\"label\":\"Enabled Datapacks\",\"type\":\"multiselect\",\"options\":[\"vanilla\",\"experiment-copper-golem\",\"experiment-penwing\",\"experiment-crab\"]}", json); + } + + @Test + public void testDeserializeSelectOption() { + String json = "{\"key\":\"gamemode\",\"label\":\"Gamemode\",\"type\":\"select\",\"value\":\"survival\",\"options\":[\"survival\",\"creative\",\"adventure\",\"spectator\"]}"; + ConfigOption option = gson.fromJson(json, ConfigOption.class); + Assertions.assertNotNull(option); + Assertions.assertEquals("gamemode", option.getKey()); + Assertions.assertEquals("Gamemode", option.getLabel()); + Assertions.assertEquals(OptionType.SELECT, option.getType()); + Assertions.assertEquals("survival", option.getValue()); + Assertions.assertArrayEquals(new String[]{"survival","creative","adventure","spectator"}, option.getOptions()); + } + + @Test + public void testSerializeSelectOption() { + ConfigOption option = new StringConfigOption("gamemode", "survival", "Gamemode", new String[]{"survival","creative","adventure","spectator"}); + String json = gson.toJson(option); + Assertions.assertNotNull(json); + Assertions.assertEquals("{\"value\":\"survival\",\"key\":\"gamemode\",\"label\":\"Gamemode\",\"type\":\"string\",\"options\":[\"survival\",\"creative\",\"adventure\",\"spectator\"]}", json); + } +} diff --git a/src/test/java/GetConfigTest.java b/src/test/java/GetConfigTest.java new file mode 100644 index 0000000..08d7d73 --- /dev/null +++ b/src/test/java/GetConfigTest.java @@ -0,0 +1,29 @@ +import com.exaroton.api.ExarotonClient; +import com.exaroton.api.server.Server; +import com.exaroton.api.server.ServerFile; +import com.exaroton.api.server.config.ConfigOption; +import com.exaroton.api.server.config.ServerConfig; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class GetConfigTest { + private static final ExarotonClient client = new ExarotonClient(System.getenv("EXAROTON_API_TOKEN")); + + @Test + public void testGetConfig() { + Server server = client.getServer(System.getenv("EXAROTON_TEST_SERVER")); + Assertions. assertDoesNotThrow(() -> { + ServerFile serverProperties = server.getFile("server.properties"); + Assertions.assertNotNull(serverProperties); + ServerConfig config = serverProperties.getConfig(); + Assertions.assertNotNull(config); + + ConfigOption gamemodeOption = config.getOption("gamemode"); + Assertions.assertNotNull(gamemodeOption); + Assertions.assertEquals("Gamemode", gamemodeOption.getLabel()); + Assertions.assertNotNull(gamemodeOption.getValue()); + Assertions.assertNotNull(gamemodeOption.getOptions()); + Assertions.assertTrue(gamemodeOption.getOptions().length > 3); + }); + } +}