Skip to content

Commit be3f88d

Browse files
committed
Implement joining and spectating
1 parent 45c11f2 commit be3f88d

File tree

6 files changed

+357
-32
lines changed

6 files changed

+357
-32
lines changed

pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@
2424
<artifactId>gson</artifactId>
2525
<version>2.9.1</version>
2626
</dependency>
27+
<dependency>
28+
<groupId>org.apache.commons</groupId>
29+
<artifactId>commons-lang3</artifactId>
30+
<version>3.12.0</version>
31+
</dependency>
32+
<dependency>
33+
<groupId>commons-io</groupId>
34+
<artifactId>commons-io</artifactId>
35+
<version>2.11.0</version>
36+
</dependency>
2737
</dependencies>
2838

2939
<build>

src/main/java/org/codegame/client/Api.java

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import java.io.IOException;
44
import java.io.InputStreamReader;
5-
import java.lang.reflect.ParameterizedType;
65
import java.lang.reflect.Type;
76
import java.net.HttpURLConnection;
7+
import java.net.URI;
88
import java.net.URL;
9+
import java.net.http.HttpClient;
10+
import java.net.http.WebSocket;
911
import java.util.Arrays;
1012
import java.util.HashMap;
1113

@@ -22,7 +24,7 @@ public class Api {
2224
private boolean tls;
2325
private String baseURL;
2426

25-
private static Gson json = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
27+
static Gson json = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
2628
.create();
2729

2830
public Api(String url) {
@@ -65,10 +67,6 @@ public class GameData {
6567
public String joinSecret;
6668
}
6769

68-
public GameData createGame(boolean makePublic, boolean protect) throws IOException {
69-
return createGame(makePublic, protect, null);
70-
}
71-
7270
private class CreateGameRequest {
7371
@SerializedName("public")
7472
public boolean makePublic;
@@ -78,7 +76,7 @@ private class CreateGameRequest {
7876
public Object config;
7977
}
8078

81-
public GameData createGame(boolean makePublic, boolean protect, Object config) throws IOException {
79+
GameData createGame(boolean makePublic, boolean protect, Object config) throws IOException {
8280
var data = new CreateGameRequest();
8381
data.makePublic = makePublic;
8482
data.protect = protect;
@@ -93,7 +91,7 @@ public class PlayerData {
9391
public String secret;
9492
}
9593

96-
public PlayerData createPlayer(String gameId, String username) throws IOException {
94+
PlayerData createPlayer(String gameId, String username) throws IOException {
9795
return createPlayer(gameId, username, "");
9896
}
9997

@@ -104,7 +102,7 @@ private class CreatePlayerRequest {
104102
public String joinSecret;
105103
}
106104

107-
public PlayerData createPlayer(String gameId, String username, String joinSecret) throws IOException {
105+
PlayerData createPlayer(String gameId, String username, String joinSecret) throws IOException {
108106
var data = new CreatePlayerRequest();
109107
data.username = username;
110108
data.joinSecret = joinSecret;
@@ -116,28 +114,38 @@ private class FetchUsernameResponse {
116114
public String username;
117115
}
118116

119-
public String fetchUsername(String gameId, String playerId) throws IOException {
117+
String fetchUsername(String gameId, String playerId) throws IOException {
120118
return fetchJSON("/api/games/" + gameId + "/players/" + playerId, FetchUsernameResponse.class).username;
121119
}
122120

123-
public HashMap<String, String> fetchPlayers(String gameId) throws IOException {
121+
HashMap<String, String> fetchPlayers(String gameId) throws IOException {
124122
return fetchJSON("/api/games/" + gameId + "/players",
125123
TypeToken.getParameterized(HashMap.class, String.class, String.class).getType());
126124
}
127125

126+
WebSocket connectWebSocket(String endpoint, WSClient.OnMessageCallback onMessage,
127+
WSClient.OnCloseCallback onClose) {
128+
return HttpClient.newHttpClient().newWebSocketBuilder()
129+
.buildAsync(URI.create(baseURL("ws", tls, url + endpoint)),
130+
new WSClient(onMessage, onClose))
131+
.join();
132+
}
133+
128134
private <T> T postJSON(String endpoint, Object requestData, Class<T> responseType) throws IOException {
129135
URL obj = new URL(this.baseURL + endpoint);
130136
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
131137
con.setRequestMethod("POST");
132138
con.setRequestProperty("Content-Type", "application/json");
133139

134140
var reqData = json.toJson(requestData);
135-
System.out.println("Data: " + reqData);
136141
con.setDoOutput(true);
137142
var os = con.getOutputStream();
138-
os.write(reqData.getBytes());
139-
os.flush();
140-
os.close();
143+
try {
144+
os.write(reqData.getBytes());
145+
os.flush();
146+
} finally {
147+
os.close();
148+
}
141149

142150
int responseCode = con.getResponseCode();
143151
if (responseCode != HttpURLConnection.HTTP_OK && responseCode != HttpURLConnection.HTTP_CREATED) {
@@ -146,8 +154,12 @@ private <T> T postJSON(String endpoint, Object requestData, Class<T> responseTyp
146154
}
147155

148156
InputStreamReader reader = new InputStreamReader(con.getInputStream());
149-
T data = json.fromJson(reader, responseType);
150-
return data;
157+
try {
158+
T data = json.fromJson(reader, responseType);
159+
return data;
160+
} finally {
161+
reader.close();
162+
}
151163
}
152164

153165
private <T> T fetchJSON(String endpoint, Class<T> responseType) throws IOException {
@@ -156,12 +168,18 @@ private <T> T fetchJSON(String endpoint, Class<T> responseType) throws IOExcepti
156168
con.setRequestMethod("GET");
157169
con.setRequestProperty("Accept", "application/json");
158170
var reader = new InputStreamReader(con.getInputStream());
159-
int responseCode = con.getResponseCode();
160-
if (responseCode != HttpURLConnection.HTTP_OK)
161-
throw new IOException("Failed to read response from " + endpoint + " endpoint: unexpected response code: "
162-
+ responseCode);
163-
T data = json.fromJson(reader, responseType);
164-
return data;
171+
try {
172+
int responseCode = con.getResponseCode();
173+
if (responseCode != HttpURLConnection.HTTP_OK) {
174+
throw new IOException(
175+
"Failed to read response from " + endpoint + " endpoint: unexpected response code: "
176+
+ responseCode);
177+
}
178+
T data = json.fromJson(reader, responseType);
179+
return data;
180+
} finally {
181+
reader.close();
182+
}
165183
}
166184

167185
private <T> T fetchJSON(String endpoint, Type responseType) throws IOException {
@@ -170,12 +188,19 @@ private <T> T fetchJSON(String endpoint, Type responseType) throws IOException {
170188
con.setRequestMethod("GET");
171189
con.setRequestProperty("Accept", "application/json");
172190
var reader = new InputStreamReader(con.getInputStream());
173-
int responseCode = con.getResponseCode();
174-
if (responseCode != HttpURLConnection.HTTP_OK)
175-
throw new IOException("Failed to read response from " + endpoint + " endpoint: unexpected response code: "
176-
+ responseCode);
177-
T data = json.fromJson(reader, responseType);
178-
return data;
191+
try {
192+
int responseCode = con.getResponseCode();
193+
if (responseCode != HttpURLConnection.HTTP_OK) {
194+
reader.close();
195+
throw new IOException(
196+
"Failed to read response from " + endpoint + " endpoint: unexpected response code: "
197+
+ responseCode);
198+
}
199+
T data = json.fromJson(reader, responseType);
200+
return data;
201+
} finally {
202+
reader.close();
203+
}
179204
}
180205

181206
static String trimURL(String url) {
@@ -198,10 +223,17 @@ static boolean isTLS(String trimmedURL) {
198223
try {
199224
var url = new URL(baseURL("http", true, trimmedURL) + "/api/info");
200225
var connection = (HttpsURLConnection) url.openConnection();
201-
connection.getInputStream();
202-
if (connection.getSSLSession().isEmpty())
226+
var stream = connection.getInputStream();
227+
if (connection.getSSLSession().isEmpty()) {
228+
stream.close();
229+
return false;
230+
}
231+
if (!connection.getSSLSession().get().isValid()) {
232+
stream.close();
203233
return false;
204-
return connection.getSSLSession().get().isValid();
234+
}
235+
stream.close();
236+
return true;
205237
} catch (IOException e) {
206238
return false;
207239
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.codegame.client;
2+
3+
import org.apache.commons.lang3.SystemUtils;
4+
5+
public class Dirs {
6+
public static String UserHome() {
7+
return System.getProperty("user.home");
8+
}
9+
10+
public static String DataHome() {
11+
if (SystemUtils.IS_OS_WINDOWS)
12+
return DataHomeWindows();
13+
else if (SystemUtils.IS_OS_MAC)
14+
return DataHomeMacOS();
15+
return DataHomeXDG();
16+
}
17+
18+
private static String DataHomeWindows() {
19+
return SystemUtils.getEnvironmentVariable("LOCALAPPDATA", UserHome() + "/AppData/Local");
20+
}
21+
22+
private static String DataHomeMacOS() {
23+
return SystemUtils.getEnvironmentVariable("XDG_DATA_HOME", UserHome() + "/Library/Application Support");
24+
}
25+
26+
private static String DataHomeXDG() {
27+
return SystemUtils.getEnvironmentVariable("XDG_DATA_HOME", UserHome() + "/.local/share");
28+
}
29+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,108 @@
11
package org.codegame.client;
22

3+
import java.io.IOException;
4+
import java.net.http.WebSocket;
5+
import java.util.HashMap;
6+
import java.util.concurrent.CountDownLatch;
7+
8+
import org.codegame.client.Api.GameInfo;
9+
310
public class GameSocket {
411
private Api api;
12+
private Session session = new Session();
13+
private WebSocket websocket;
14+
private HashMap<String, String> usernameCache = new HashMap<>();
15+
private CountDownLatch exitEvent = new CountDownLatch(1);
516

617
public GameSocket(String url) {
718
api = new Api(url);
819
}
920

21+
public Api.GameData createGame(boolean makePublic, boolean protect, Object config) throws IOException {
22+
return api.createGame(makePublic, protect, config);
23+
}
24+
25+
public void join(String gameId, String username) throws IOException {
26+
join(gameId, username, "");
27+
}
28+
29+
public void join(String gameId, String username, String joinSecret) throws IOException {
30+
if (session.gameURL != "")
31+
throw new IllegalStateException("This socket is already connected to a game.");
32+
var player = api.createPlayer(gameId, username, joinSecret);
33+
connect(gameId, player.id, player.secret);
34+
}
35+
36+
public void restoreSession(String username) throws IOException {
37+
if (session.gameURL != "")
38+
throw new IllegalStateException("This socket is already connected to a game.");
39+
var session = Session.load(api.getURL(), username);
40+
try {
41+
connect(session.gameId, session.playerId, session.playerSecret);
42+
} catch (Exception e) {
43+
session.remove();
44+
throw e;
45+
}
46+
}
47+
48+
public void connect(String gameId, String playerId, String playerSecret) throws IOException {
49+
if (session.gameURL != "")
50+
throw new IllegalStateException("This socket is already connected to a game.");
51+
52+
websocket = api.connectWebSocket(
53+
"/api/games/" + gameId + "/connect?player_id=" + playerId + "&player_secret=" + playerSecret,
54+
(String message) -> onMessage(message), () -> onClose());
55+
56+
session = new Session(api.getURL(), "", gameId, playerId, playerSecret);
57+
58+
usernameCache = api.fetchPlayers(gameId);
59+
session.username = usernameCache.get(playerId);
60+
try {
61+
session.save();
62+
} catch (Exception e) {
63+
System.err.println("ERROR: Failed to save session: " + e.getMessage());
64+
}
65+
}
66+
67+
public void connect(String gameId) throws IOException {
68+
if (session.gameURL != "")
69+
throw new IllegalStateException("This socket is already connected to a game.");
70+
71+
websocket = api.connectWebSocket(
72+
"/api/games/" + gameId + "/spectate",
73+
(String message) -> onMessage(message), () -> onClose());
74+
75+
session = new Session(api.getURL(), "", gameId, "", "");
76+
77+
usernameCache = api.fetchPlayers(gameId);
78+
}
79+
80+
public void block() {
81+
try {
82+
exitEvent.await();
83+
} catch (InterruptedException e) {
84+
e.printStackTrace();
85+
}
86+
}
87+
88+
public void close() {
89+
websocket.sendClose(WebSocket.NORMAL_CLOSURE, "Normal closure.");
90+
block();
91+
}
92+
1093
public Api getApi() {
1194
return api;
1295
}
96+
97+
public Session getSession() {
98+
return session;
99+
}
100+
101+
private void onMessage(String message) {
102+
System.out.println("Received: " + message);
103+
}
104+
105+
private void onClose() {
106+
exitEvent.countDown();
107+
}
13108
}

0 commit comments

Comments
 (0)