Added server image support.
This commit is contained in:
parent
cbcc8b3db1
commit
6df046dfea
|
@ -4,5 +4,8 @@ core/target/
|
|||
server/target/
|
||||
server-registry/target/
|
||||
*.iml
|
||||
|
||||
# Server files for testing.
|
||||
/settings.yaml
|
||||
/*.log
|
||||
/icon.png
|
||||
|
|
|
@ -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())));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -6,6 +6,7 @@ public record ServerInfoResponse(
|
|||
String updatedAt,
|
||||
String description,
|
||||
String location,
|
||||
String icon,
|
||||
int maxPlayers,
|
||||
int currentPlayers
|
||||
) {}
|
||||
|
|
|
@ -5,6 +5,7 @@ public record ServerInfoUpdate (
|
|||
String address,
|
||||
String description,
|
||||
String location,
|
||||
String icon,
|
||||
int maxPlayers,
|
||||
int currentPlayers
|
||||
) {}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue