Added server image support.

This commit is contained in:
Andrew Lalis 2021-07-06 22:23:51 +02:00
parent cbcc8b3db1
commit 6df046dfea
12 changed files with 110 additions and 26 deletions

3
.gitignore vendored
View File

@ -4,5 +4,8 @@ core/target/
server/target/
server-registry/target/
*.iml
# Server files for testing.
/settings.yaml
/*.log
/icon.png

View File

@ -8,6 +8,7 @@ public record PublicServerInfo(
String address,
String description,
String location,
Image icon,
int maxPlayers,
int currentPlayers
) {
@ -18,11 +19,18 @@ public record PublicServerInfo(
panel.add(new JLabel("Address: " + value.address()), BorderLayout.NORTH);
JPanel content = new JPanel();
if (value.icon() != null) {
JLabel iconLabel = new JLabel(new ImageIcon(value.icon()));
content.add(iconLabel);
}
JTextArea descriptionArea = new JTextArea(value.description());
descriptionArea.setEditable(false);
descriptionArea.setWrapStyleWord(true);
descriptionArea.setLineWrap(true);
panel.add(descriptionArea, BorderLayout.CENTER);
content.add(descriptionArea);
panel.add(content, BorderLayout.CENTER);
JPanel bottomPanel = new JPanel();
bottomPanel.add(new JLabel(String.format("Current players: %d / %d", value.currentPlayers(), value.maxPlayers())));

View File

@ -2,8 +2,12 @@ package nl.andrewlalis.aos_client.launcher.servers;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
@ -14,6 +18,7 @@ import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
@ -101,11 +106,17 @@ public class PublicServerListModel extends AbstractListModel<PublicServerInfo> {
this.currentPageItems.clear();
for (Iterator<JsonNode> it = json.get("contents").elements(); it.hasNext();) {
JsonNode node = it.next();
Image icon = null;
JsonNode iconNode = node.get("icon");
if (iconNode != null && iconNode.getNodeType() == JsonNodeType.STRING) {
icon = ImageIO.read(new ByteArrayInputStream(Base64.getUrlDecoder().decode(iconNode.textValue())));
}
PublicServerInfo info = new PublicServerInfo(
node.get("name").asText(),
node.get("address").asText(),
node.get("description").asText(),
node.get("location").asText(),
icon,
node.get("maxPlayers").asInt(),
node.get("currentPlayers").asInt()
);
@ -143,4 +154,8 @@ public class PublicServerListModel extends AbstractListModel<PublicServerInfo> {
public PublicServerInfo getElementAt(int index) {
return this.currentPageItems.get(index);
}
public void dispose() {
this.executorService.shutdown();
}
}

View File

@ -11,9 +11,19 @@ import java.awt.event.MouseEvent;
import java.io.IOException;
public class SearchServersDialog extends JDialog {
private final JButton prevButton;
private final JButton nextButton;
private final PublicServerListModel listModel;
public SearchServersDialog(Frame frame, ServerInfoListModel serverInfoListModel) {
super(frame, true);
this.setTitle("Search for Servers");
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.prevButton = new JButton("<");
this.nextButton = new JButton(">");
this.listModel = new PublicServerListModel(prevButton, nextButton);
this.setContentPane(this.getContent(serverInfoListModel));
this.pack();
this.setLocationRelativeTo(frame);
@ -22,10 +32,6 @@ public class SearchServersDialog extends JDialog {
private Container getContent(ServerInfoListModel serverInfoListModel) {
JPanel panel = new JPanel(new BorderLayout());
JButton prevButton = new JButton("<");
JButton nextButton = new JButton(">");
PublicServerListModel listModel = new PublicServerListModel(prevButton, nextButton);
JList<PublicServerInfo> serversList = new JList<>(listModel);
serversList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
serversList.setCellRenderer(PublicServerInfo.cellRenderer());
@ -96,4 +102,10 @@ public class SearchServersDialog extends JDialog {
JOptionPane.showMessageDialog(this, "Could not connect:\n" + e.getMessage(), "Connection Error", JOptionPane.WARNING_MESSAGE);
}
}
@Override
public void dispose() {
this.listModel.dispose();
super.dispose();
}
}

View File

@ -10,12 +10,15 @@ import nl.andrewlalis.aos_server_registry.util.Responses;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
@ -50,7 +53,7 @@ public class ServerInfoServlet extends HttpServlet {
var info = Requests.getBody(req, ServerInfoUpdate.class);
try {
this.saveNewServer(info);
Responses.ok(resp, Map.of("message", "Server info saved."));
Responses.ok(resp, Map.of("message", "Server icon saved."));
} catch (SQLException e) {
e.printStackTrace();
Responses.internalServerError(resp, "Database error.");
@ -63,11 +66,11 @@ public class ServerInfoServlet extends HttpServlet {
this.updateServerStatus(status, resp);
}
private List<ServerInfoResponse> getData(int size, int page, String searchQuery, String order, String orderDir) throws SQLException {
private List<ServerInfoResponse> getData(int size, int page, String searchQuery, String order, String orderDir) throws SQLException, IOException {
final List<ServerInfoResponse> results = new ArrayList<>(20);
var con = DataManager.getInstance().getConnection();
String selectQuery = """
SELECT name, address, updated_at, description, location, max_players, current_players
SELECT name, address, updated_at, description, location, icon, max_players, current_players
FROM servers
//CONDITIONS
ORDER BY name
@ -87,14 +90,21 @@ public class ServerInfoServlet extends HttpServlet {
stmt.setInt(index, page * size);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
// Attempt to load the server's icon, if it is not null.
InputStream iconInputStream = rs.getBinaryStream(6);
String encodedIconImage = null;
if (iconInputStream != null) {
encodedIconImage = Base64.getUrlEncoder().encodeToString(iconInputStream.readAllBytes());
}
results.add(new ServerInfoResponse(
rs.getString(1),
rs.getString(2),
rs.getTimestamp(3).toInstant().atOffset(ZoneOffset.UTC).toString(),
rs.getString(4),
rs.getString(5),
rs.getInt(6),
rs.getInt(7)
encodedIconImage,
rs.getInt(7),
rs.getInt(8)
));
}
stmt.close();
@ -111,30 +121,40 @@ public class ServerInfoServlet extends HttpServlet {
stmt.close();
if (!exists) {
PreparedStatement createStmt = con.prepareStatement("""
INSERT INTO servers (name, address, description, location, max_players, current_players)
VALUES (?, ?, ?, ?, ?, ?);
INSERT INTO servers (name, address, description, location, icon, max_players, current_players)
VALUES (?, ?, ?, ?, ?, ?, ?);
""");
createStmt.setString(1, info.name());
createStmt.setString(2, info.address());
createStmt.setString(3, info.description());
createStmt.setString(4, info.location());
createStmt.setInt(5, info.maxPlayers());
createStmt.setInt(6, info.currentPlayers());
InputStream inputStream = null;
if (info.icon() != null) {
inputStream = new ByteArrayInputStream(Base64.getUrlDecoder().decode(info.icon()));
}
createStmt.setBinaryStream(5, inputStream);
createStmt.setInt(6, info.maxPlayers());
createStmt.setInt(7, info.currentPlayers());
int rowCount = createStmt.executeUpdate();
createStmt.close();
if (rowCount != 1) throw new SQLException("Could not insert new server.");
log.info("Registered new server " + info.name() + " @ " + info.address());
} else {
PreparedStatement updateStmt = con.prepareStatement("""
UPDATE servers SET description = ?, location = ?, max_players = ?, current_players = ?
UPDATE servers SET description = ?, location = ?, icon = ?, max_players = ?, current_players = ?
WHERE name = ? AND address = ?;
""");
updateStmt.setString(1, info.description());
updateStmt.setString(2, info.location());
updateStmt.setInt(3, info.maxPlayers());
updateStmt.setInt(4, info.currentPlayers());
updateStmt.setString(5, info.name());
updateStmt.setString(6, info.address());
InputStream inputStream = null;
if (info.icon() != null) {
inputStream = new ByteArrayInputStream(Base64.getUrlDecoder().decode(info.icon()));
}
updateStmt.setBinaryStream(3, inputStream);
updateStmt.setInt(4, info.maxPlayers());
updateStmt.setInt(5, info.currentPlayers());
updateStmt.setString(6, info.name());
updateStmt.setString(7, info.address());
int rowCount = updateStmt.executeUpdate();
updateStmt.close();
if (rowCount != 1) throw new SQLException("Could not update server.");

View File

@ -6,6 +6,7 @@ public record ServerInfoResponse(
String updatedAt,
String description,
String location,
String icon,
int maxPlayers,
int currentPlayers
) {}

View File

@ -5,6 +5,7 @@ public record ServerInfoUpdate (
String address,
String description,
String location,
String icon,
int maxPlayers,
int currentPlayers
) {}

View File

@ -7,6 +7,7 @@ CREATE TABLE servers (
updated_at TIMESTAMP(0) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP(0),
description VARCHAR(1024),
location VARCHAR(64),
icon BLOB NULL DEFAULT NULL,
max_players INTEGER NOT NULL,
current_players INTEGER NOT NULL,

View File

@ -2,11 +2,19 @@ package nl.andrewlalis.aos_server;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
@ -48,6 +56,7 @@ public class RegistryManager {
data.put("address", this.server.getSettings().getRegistrySettings().getAddress());
data.put("description", this.server.getSettings().getRegistrySettings().getDescription());
data.put("location", this.server.getSettings().getRegistrySettings().getLocation());
data.put("icon", this.getIconData());
data.put("maxPlayers", this.server.getSettings().getMaxPlayers());
data.put("currentPlayers", 0);
HttpRequest request = HttpRequest.newBuilder()
@ -84,6 +93,20 @@ public class RegistryManager {
}
}
private String getIconData() throws IOException {
Path iconFile = Path.of("icon.png");
if (Files.exists(iconFile)) {
byte[] imageBytes = Files.readAllBytes(iconFile);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
if (image.getWidth() == 64 && image.getHeight() == 64) {
return Base64.getUrlEncoder().encodeToString(imageBytes);
} else {
System.err.println("icon.png must be 64 x 64.");
}
}
return null;
}
public void shutdown() {
this.executorService.shutdown();
try {

View File

@ -1,9 +1,5 @@
package nl.andrewlalis.aos_server;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import nl.andrewlalis.aos_core.geom.Vec2;
import nl.andrewlalis.aos_core.model.*;
import nl.andrewlalis.aos_core.model.tools.GunCategory;
@ -26,9 +22,7 @@ import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class Server {
private final ServerSettings settings;

View File

@ -11,6 +11,10 @@ import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Utility class that's responsible for loading the settings from their usual
* location,
*/
public class SettingsLoader {
public static ServerSettings load() throws IOException {
Path settingsFile = Path.of("settings.yaml");

View File

@ -28,6 +28,8 @@ registry-settings:
description: "A simple testing server for development."
# Location of this server, to help players choose servers near to them.
location: "Earth"
# Note: To set an icon for this server, add an "icon.png" image to the server's directory (where the settings are).
# The icon MUST be 64x64 pixels in size.
# Settings that control player behavior.