Added version tracking to client and server and updated registry.

This commit is contained in:
Andrew Lalis 2021-07-08 22:59:16 +02:00
parent c6d54d3f38
commit ec86b8cf69
16 changed files with 118 additions and 48 deletions

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<version>5.0</version> <version>0.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -6,6 +6,7 @@ import java.awt.*;
public record PublicServerInfo( public record PublicServerInfo(
String name, String name,
String address, String address,
String version,
String description, String description,
String location, String location,
Image icon, Image icon,
@ -14,35 +15,53 @@ public record PublicServerInfo(
) { ) {
public static ListCellRenderer<PublicServerInfo> cellRenderer() { public static ListCellRenderer<PublicServerInfo> cellRenderer() {
return (list, value, index, isSelected, cellHasFocus) -> { return (list, value, index, isSelected, cellHasFocus) -> {
JPanel panel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createTitledBorder(value.name())); var c = new GridBagConstraints();
panel.add(new JLabel("Address: " + value.address()), BorderLayout.NORTH); c.anchor = GridBagConstraints.CENTER;
c.insets = new Insets(1, 1, 1, 1);
JPanel content = new JPanel(); c.gridx = 0;
c.gridy = 0;
c.gridheight = 4;
c.weightx = 0.25;
c.fill = GridBagConstraints.BOTH;
if (value.icon() != null) { if (value.icon() != null) {
JLabel iconLabel = new JLabel(new ImageIcon(value.icon())); JLabel iconLabel = new JLabel(new ImageIcon(value.icon()));
content.add(iconLabel); panel.add(iconLabel, c);
} }
c.anchor = GridBagConstraints.LINE_START;
c.gridx = 1;
c.gridy = 0;
c.gridheight = 1;
c.weightx = 0.75;
c.fill = GridBagConstraints.HORIZONTAL;
var nameLabel = new JLabel(value.name() + " [" + value.version() + "]");
nameLabel.setFont(nameLabel.getFont().deriveFont(Font.BOLD));
panel.add(nameLabel, c);
c.gridy++;
var addressLabel = new JLabel(value.address());
addressLabel.setFont(new Font("monospaced", Font.PLAIN, 12));
panel.add(addressLabel, c);
c.gridy++;
JTextArea descriptionArea = new JTextArea(value.description()); JTextArea descriptionArea = new JTextArea(value.description());
descriptionArea.setEditable(false); descriptionArea.setEditable(false);
descriptionArea.setWrapStyleWord(true); descriptionArea.setWrapStyleWord(true);
descriptionArea.setLineWrap(true); descriptionArea.setLineWrap(true);
content.add(descriptionArea); panel.add(descriptionArea, c);
panel.add(content, BorderLayout.CENTER); c.gridy++;
panel.add(new JLabel(String.format("%d / %d Players", value.currentPlayers(), value.maxPlayers())), c);
JPanel bottomPanel = new JPanel();
bottomPanel.add(new JLabel(String.format("Current players: %d / %d", value.currentPlayers(), value.maxPlayers())));
panel.add(bottomPanel, BorderLayout.SOUTH);
if (isSelected) { if (isSelected) {
panel.setBackground(list.getSelectionBackground()); panel.setBackground(list.getSelectionBackground());
panel.setForeground(list.getSelectionForeground()); panel.setForeground(list.getSelectionForeground());
descriptionArea.setForeground(list.getSelectionForeground());
descriptionArea.setBackground(list.getSelectionBackground());
} else { } else {
panel.setBackground(list.getBackground()); panel.setBackground(list.getBackground());
panel.setForeground(list.getForeground()); panel.setForeground(list.getForeground());
descriptionArea.setForeground(list.getForeground());
descriptionArea.setBackground(list.getBackground());
} }
return panel; return panel;

View File

@ -149,6 +149,7 @@ public class PublicServerListModel extends AbstractListModel<PublicServerInfo> {
PublicServerInfo info = new PublicServerInfo( PublicServerInfo info = new PublicServerInfo(
node.get("name").asText(), node.get("name").asText(),
node.get("address").asText(), node.get("address").asText(),
node.get("version").asText(),
node.get("description").asText(), node.get("description").asText(),
node.get("location").asText(), node.get("location").asText(),
icon, icon,

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<version>5.0</version> <version>0.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -7,7 +7,7 @@
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>5.0</version> <version>0.5.0</version>
<modules> <modules>
<module>server</module> <module>server</module>
<module>client</module> <module>client</module>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<version>5.0</version> <version>0.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -0,0 +1,21 @@
package nl.andrewlalis.aos_server_registry.servlet;
public class ResponseStatusException extends Exception {
private final int statusCode;
private final String message;
public ResponseStatusException(int statusCode, String message) {
super(message);
this.statusCode = statusCode;
this.message = message;
}
public int getStatusCode() {
return statusCode;
}
@Override
public String getMessage() {
return message;
}
}

View File

@ -40,7 +40,7 @@ public class ServerInfoServlet extends HttpServlet {
)); ));
String orderDir = Requests.getStringParam(req, "dir", "ASC", s -> s.equalsIgnoreCase("ASC") || s.equalsIgnoreCase("DESC")); String orderDir = Requests.getStringParam(req, "dir", "ASC", s -> s.equalsIgnoreCase("ASC") || s.equalsIgnoreCase("DESC"));
try { try {
var results = this.getData(size, page, searchQuery, order, orderDir); var results = this.getData(size, page, searchQuery, null, order, orderDir);
Responses.ok(resp, new Page<>(results, page, size, order, orderDir)); Responses.ok(resp, new Page<>(results, page, size, order, orderDir));
} catch (SQLException t) { } catch (SQLException t) {
t.printStackTrace(); t.printStackTrace();
@ -54,6 +54,8 @@ public class ServerInfoServlet extends HttpServlet {
try { try {
this.saveNewServer(info); this.saveNewServer(info);
Responses.ok(resp, Map.of("message", "Server icon saved.")); Responses.ok(resp, Map.of("message", "Server icon saved."));
} catch (ResponseStatusException e) {
Responses.json(resp, e.getStatusCode(), Map.of("message", e.getMessage()));
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
Responses.internalServerError(resp, "Database error."); Responses.internalServerError(resp, "Database error.");
@ -66,11 +68,11 @@ public class ServerInfoServlet extends HttpServlet {
this.updateServerStatus(status, resp); this.updateServerStatus(status, resp);
} }
private List<ServerInfoResponse> getData(int size, int page, String searchQuery, String order, String orderDir) throws SQLException, IOException { private List<ServerInfoResponse> getData(int size, int page, String searchQuery, String versionQuery, String order, String orderDir) throws SQLException, IOException {
final List<ServerInfoResponse> results = new ArrayList<>(20); final List<ServerInfoResponse> results = new ArrayList<>(20);
var con = DataManager.getInstance().getConnection(); var con = DataManager.getInstance().getConnection();
String selectQuery = """ String selectQuery = """
SELECT name, address, updated_at, description, location, icon, max_players, current_players SELECT name, address, version, updated_at, description, location, icon, max_players, current_players
FROM servers FROM servers
//CONDITIONS //CONDITIONS
ORDER BY name ORDER BY name
@ -78,20 +80,30 @@ public class ServerInfoServlet extends HttpServlet {
OFFSET ? OFFSET ?
"""; """;
selectQuery = selectQuery.replace("ORDER BY name", "ORDER BY " + order + " " + orderDir); selectQuery = selectQuery.replace("ORDER BY name", "ORDER BY " + order + " " + orderDir);
List<String> conditions = new ArrayList<>();
List<Object> conditionParams = new ArrayList<>();
if (searchQuery != null && !searchQuery.isBlank()) { if (searchQuery != null && !searchQuery.isBlank()) {
selectQuery = selectQuery.replace("//CONDITIONS", "WHERE UPPER(name) LIKE ?"); conditions.add("UPPER(name) LIKE ?");
conditionParams.add("%" + searchQuery.toUpperCase() + "%");
}
if (versionQuery != null && !versionQuery.isBlank()) {
conditions.add("version = ?");
conditionParams.add(versionQuery);
}
if (!conditions.isEmpty()) {
selectQuery = selectQuery.replace("//CONDITIONS", "WHERE " + String.join(" AND ", conditions));
} }
PreparedStatement stmt = con.prepareStatement(selectQuery); PreparedStatement stmt = con.prepareStatement(selectQuery);
int index = 1; int index = 1;
if (searchQuery != null && !searchQuery.isBlank()) { for (var param : conditionParams) {
stmt.setString(index++, "%" + searchQuery.toUpperCase() + "%"); stmt.setObject(index++, param);
} }
stmt.setInt(index++, size); stmt.setInt(index++, size);
stmt.setInt(index, page * size); stmt.setInt(index, page * size);
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
while (rs.next()) { while (rs.next()) {
// Attempt to load the server's icon, if it is not null. // Attempt to load the server's icon, if it is not null.
InputStream iconInputStream = rs.getBinaryStream(6); InputStream iconInputStream = rs.getBinaryStream(7);
String encodedIconImage = null; String encodedIconImage = null;
if (iconInputStream != null) { if (iconInputStream != null) {
encodedIconImage = Base64.getUrlEncoder().encodeToString(iconInputStream.readAllBytes()); encodedIconImage = Base64.getUrlEncoder().encodeToString(iconInputStream.readAllBytes());
@ -99,19 +111,20 @@ public class ServerInfoServlet extends HttpServlet {
results.add(new ServerInfoResponse( results.add(new ServerInfoResponse(
rs.getString(1), rs.getString(1),
rs.getString(2), rs.getString(2),
rs.getTimestamp(3).toInstant().atOffset(ZoneOffset.UTC).toString(), rs.getString(3),
rs.getString(4), rs.getTimestamp(4).toInstant().atOffset(ZoneOffset.UTC).toString(),
rs.getString(5), rs.getString(5),
rs.getString(6),
encodedIconImage, encodedIconImage,
rs.getInt(7), rs.getInt(8),
rs.getInt(8) rs.getInt(9)
)); ));
} }
stmt.close(); stmt.close();
return results; return results;
} }
private void saveNewServer(ServerInfoUpdate info) throws SQLException { private void saveNewServer(ServerInfoUpdate info) throws SQLException, ResponseStatusException {
var con = DataManager.getInstance().getConnection(); var con = DataManager.getInstance().getConnection();
PreparedStatement stmt = con.prepareStatement("SELECT name, address FROM servers WHERE name = ? AND address = ?"); PreparedStatement stmt = con.prepareStatement("SELECT name, address FROM servers WHERE name = ? AND address = ?");
stmt.setString(1, info.name()); stmt.setString(1, info.name());
@ -119,42 +132,45 @@ public class ServerInfoServlet extends HttpServlet {
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
boolean exists = rs.next(); boolean exists = rs.next();
stmt.close(); stmt.close();
String version = info.version() == null ? "Unknown" : info.version();
if (!exists) { if (!exists) {
PreparedStatement createStmt = con.prepareStatement(""" PreparedStatement createStmt = con.prepareStatement("""
INSERT INTO servers (name, address, description, location, icon, max_players, current_players) INSERT INTO servers (name, address, version, description, location, icon, max_players, current_players)
VALUES (?, ?, ?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?, ?, ?, ?);
"""); """);
createStmt.setString(1, info.name()); createStmt.setString(1, info.name());
createStmt.setString(2, info.address()); createStmt.setString(2, info.address());
createStmt.setString(3, info.description()); createStmt.setString(3, version);
createStmt.setString(4, info.location()); createStmt.setString(4, info.description());
createStmt.setString(5, info.location());
InputStream inputStream = null; InputStream inputStream = null;
if (info.icon() != null) { if (info.icon() != null) {
inputStream = new ByteArrayInputStream(Base64.getUrlDecoder().decode(info.icon())); inputStream = new ByteArrayInputStream(Base64.getUrlDecoder().decode(info.icon()));
} }
createStmt.setBinaryStream(5, inputStream); createStmt.setBinaryStream(6, inputStream);
createStmt.setInt(6, info.maxPlayers()); createStmt.setInt(7, info.maxPlayers());
createStmt.setInt(7, info.currentPlayers()); createStmt.setInt(8, info.currentPlayers());
int rowCount = createStmt.executeUpdate(); int rowCount = createStmt.executeUpdate();
createStmt.close(); createStmt.close();
if (rowCount != 1) throw new SQLException("Could not insert new server."); if (rowCount != 1) throw new SQLException("Could not insert new server.");
log.info("Registered new server " + info.name() + " @ " + info.address()); log.info("Registered new server " + info.name() + " @ " + info.address() + " running version " + version + ".");
} else { } else {
PreparedStatement updateStmt = con.prepareStatement(""" PreparedStatement updateStmt = con.prepareStatement("""
UPDATE servers SET description = ?, location = ?, icon = ?, max_players = ?, current_players = ? UPDATE servers SET version = ?, description = ?, location = ?, icon = ?, max_players = ?, current_players = ?
WHERE name = ? AND address = ?; WHERE name = ? AND address = ?;
"""); """);
updateStmt.setString(1, info.description()); updateStmt.setString(1, version);
updateStmt.setString(2, info.location()); updateStmt.setString(2, info.description());
updateStmt.setString(3, info.location());
InputStream inputStream = null; InputStream inputStream = null;
if (info.icon() != null) { if (info.icon() != null) {
inputStream = new ByteArrayInputStream(Base64.getUrlDecoder().decode(info.icon())); inputStream = new ByteArrayInputStream(Base64.getUrlDecoder().decode(info.icon()));
} }
updateStmt.setBinaryStream(3, inputStream); updateStmt.setBinaryStream(4, inputStream);
updateStmt.setInt(4, info.maxPlayers()); updateStmt.setInt(5, info.maxPlayers());
updateStmt.setInt(5, info.currentPlayers()); updateStmt.setInt(6, info.currentPlayers());
updateStmt.setString(6, info.name()); updateStmt.setString(7, info.name());
updateStmt.setString(7, info.address()); updateStmt.setString(8, info.address());
int rowCount = updateStmt.executeUpdate(); int rowCount = updateStmt.executeUpdate();
updateStmt.close(); updateStmt.close();
if (rowCount != 1) throw new SQLException("Could not update server."); if (rowCount != 1) throw new SQLException("Could not update server.");

View File

@ -3,6 +3,7 @@ package nl.andrewlalis.aos_server_registry.servlet.dto;
public record ServerInfoResponse( public record ServerInfoResponse(
String name, String name,
String address, String address,
String version,
String updatedAt, String updatedAt,
String description, String description,
String location, String location,

View File

@ -3,6 +3,7 @@ package nl.andrewlalis.aos_server_registry.servlet.dto;
public record ServerInfoUpdate ( public record ServerInfoUpdate (
String name, String name,
String address, String address,
String version,
String description, String description,
String location, String location,
String icon, String icon,

View File

@ -21,6 +21,12 @@ public class Responses {
mapper.writeValue(resp.getOutputStream(), body); mapper.writeValue(resp.getOutputStream(), body);
} }
public static void json(HttpServletResponse resp, int status, Object body) throws IOException {
resp.setStatus(status);
resp.setContentType("application/json");
mapper.writeValue(resp.getOutputStream(), body);
}
public static void badRequest(HttpServletResponse resp, String msg) throws IOException { public static void badRequest(HttpServletResponse resp, String msg) throws IOException {
respond(resp, HttpServletResponse.SC_BAD_REQUEST, msg); respond(resp, HttpServletResponse.SC_BAD_REQUEST, msg);
} }

View File

@ -3,10 +3,11 @@ SET MODE MySQL;
CREATE TABLE servers ( CREATE TABLE servers (
name VARCHAR(64) NOT NULL, name VARCHAR(64) NOT NULL,
address VARCHAR(255) NOT NULL, address VARCHAR(255) NOT NULL,
version VARCHAR(64) NOT NULL DEFAULT 'Unknown Version',
created_at TIMESTAMP(0) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP(0), created_at TIMESTAMP(0) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP(0),
updated_at TIMESTAMP(0) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP(0), updated_at TIMESTAMP(0) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP(0),
description VARCHAR(1024), description VARCHAR(1024),
location VARCHAR(64), location VARCHAR(128),
icon BLOB NULL DEFAULT NULL, icon BLOB NULL DEFAULT NULL,
max_players INTEGER NOT NULL, max_players INTEGER NOT NULL,
@ -17,3 +18,4 @@ CREATE TABLE servers (
); );
CREATE INDEX server_name_idx ON servers(name); CREATE INDEX server_name_idx ON servers(name);
CREATE INDEX server_version_idx ON servers(version);

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<version>5.0</version> <version>0.5.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -66,6 +66,7 @@ public class RegistryManager {
Map<String, Object> data = new HashMap<>(); Map<String, Object> data = new HashMap<>();
data.put("name", this.server.getSettings().getRegistrySettings().getName()); data.put("name", this.server.getSettings().getRegistrySettings().getName());
data.put("address", this.server.getSettings().getRegistrySettings().getAddress()); data.put("address", this.server.getSettings().getRegistrySettings().getAddress());
data.put("version", Server.VERSION);
data.put("description", this.server.getSettings().getRegistrySettings().getDescription()); data.put("description", this.server.getSettings().getRegistrySettings().getDescription());
data.put("location", this.server.getSettings().getRegistrySettings().getLocation()); data.put("location", this.server.getSettings().getRegistrySettings().getLocation());
data.put("icon", this.getIconData()); data.put("icon", this.getIconData());

View File

@ -25,6 +25,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
public class Server { public class Server {
public static final String VERSION = "0.5.0";
private final ServerSettings settings; private final ServerSettings settings;
private final List<ClientHandler> clientHandlers; private final List<ClientHandler> clientHandlers;
@ -263,6 +264,7 @@ public class Server {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
System.out.println("Starting Ace of Shades Server version " + VERSION + ".");
Server server = new Server(SettingsLoader.load()); Server server = new Server(SettingsLoader.load());
server.run(); server.run();
} }

View File

@ -43,7 +43,7 @@ public class ServerCli extends Thread {
public void run() { public void run() {
this.running = true; this.running = true;
String input; String input;
System.out.println("Server command-line-interface initialized. Type \"help\" for more information."); System.out.println("AOS-Server command-line-interface initialized. Type \"help\" for more information.");
while (this.running) { while (this.running) {
try { try {
input = reader.readLine(); input = reader.readLine();