Skip to content

Commit 299c3e7

Browse files
committed
Great improvements on the API client
1 parent 4d87a23 commit 299c3e7

4 files changed

Lines changed: 298 additions & 54 deletions

File tree

api-client/src/main/java/xyz.gianlu.librespot.api.client/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public void start(Stage primaryStage) throws Exception {
1919
FXMLLoader loader = new FXMLLoader();
2020
Parent root = loader.load(getClass().getClassLoader().getResourceAsStream("main.fxml"));
2121
primaryStage.setTitle("librespot-java API client");
22-
primaryStage.setScene(new Scene(root, 600, 400));
22+
primaryStage.setScene(new Scene(root, 600, 600));
2323
primaryStage.show();
2424
}
2525
}

api-client/src/main/java/xyz.gianlu.librespot.api.client/MainController.java

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
import com.google.gson.JsonObject;
44
import com.sun.javafx.collections.ObservableListWrapper;
5-
import javafx.scene.control.Button;
6-
import javafx.scene.control.ListView;
7-
import javafx.scene.control.TextArea;
8-
import javafx.scene.control.TextField;
5+
import javafx.beans.property.SimpleStringProperty;
6+
import javafx.scene.control.*;
7+
import javafx.scene.control.cell.TextFieldTableCell;
98
import javafx.scene.input.MouseEvent;
9+
import javafx.scene.layout.GridPane;
10+
import javafx.scene.layout.Priority;
1011
import org.jetbrains.annotations.NotNull;
1112

12-
import java.util.ArrayList;
13+
import java.io.PrintWriter;
14+
import java.io.StringWriter;
15+
import java.net.URI;
16+
import java.net.URISyntaxException;
17+
import java.util.*;
1318
import java.util.concurrent.ThreadLocalRandom;
1419

1520
/**
@@ -23,17 +28,91 @@ public class MainController implements NetworkThread.Listener, NetworkThread.Cal
2328
public TextField jsonrpcMethod;
2429
public TextArea jsonrpcParams;
2530
public ListView<String> responses;
31+
public TextField mercuryContentType;
32+
public TextField mercuryUri;
33+
public ComboBox<String> mercuryMethod;
34+
public TitledPane receivedContainer;
35+
public TitledPane sendContainer;
36+
public TableView<Header> mercuryHeaders;
37+
public TableColumn<Header, String> mercuryHeaderKeys;
38+
public TableColumn<Header, String> mercuryHeaderValues;
2639
private NetworkThread networkThread;
2740

2841
public MainController() {
2942
}
3043

3144
public void initialize() {
3245
responses.setItems(new ObservableListWrapper<>(new ArrayList<>()));
46+
mercuryMethod.setItems(new ObservableListWrapper<>(Arrays.asList("GET", "SUB", "UNSUB", "SEND")));
47+
sendContainer.setDisable(true);
48+
receivedContainer.setDisable(true);
49+
50+
mercuryHeaderKeys.setCellValueFactory(param -> param.getValue().key);
51+
mercuryHeaderKeys.setCellFactory(TextFieldTableCell.forTableColumn());
52+
mercuryHeaderKeys.setOnEditCommit(t -> {
53+
Header header = t.getTableView().getItems().get(t.getTablePosition().getRow());
54+
header.key.set(t.getNewValue());
55+
56+
if (header.key.isEmpty().get() && header.value.isEmpty().get())
57+
t.getTableView().getItems().remove(header);
58+
});
59+
60+
mercuryHeaderValues.setCellValueFactory(param -> param.getValue().value);
61+
mercuryHeaderValues.setCellFactory(TextFieldTableCell.forTableColumn());
62+
mercuryHeaderValues.setOnEditCommit(t -> {
63+
Header header = t.getTableView().getItems().get(t.getTablePosition().getRow());
64+
header.value.set(t.getNewValue());
65+
66+
if (header.key.isEmpty().get() && header.value.isEmpty().get())
67+
t.getTableView().getItems().remove(header);
68+
});
69+
70+
mercuryHeaders.setItems(new ObservableListWrapper<>(new ArrayList<>()));
3371
}
3472

3573
public void clickedConnect(MouseEvent mouseEvent) {
36-
networkThread = new NetworkThread(address.getText(), this);
74+
try {
75+
networkThread = new NetworkThread(new URI(address.getText()), this);
76+
} catch (URISyntaxException ex) {
77+
showError(ex);
78+
}
79+
}
80+
81+
private void showError(String msg) {
82+
Alert alert = new Alert(Alert.AlertType.ERROR);
83+
alert.setTitle("An error occurred!");
84+
alert.setHeaderText("Look, an Error Dialog");
85+
alert.setContentText("Ooops, there was an error!");
86+
87+
alert.showAndWait();
88+
}
89+
90+
private void showError(@NotNull Throwable throwable) {
91+
Alert alert = new Alert(Alert.AlertType.ERROR);
92+
alert.setTitle("An error occurred!");
93+
alert.setHeaderText(throwable.getMessage());
94+
95+
StringWriter sw = new StringWriter();
96+
throwable.printStackTrace(new PrintWriter(sw));
97+
String exceptionText = sw.toString();
98+
99+
Label label = new Label("The exception stacktrace was:");
100+
TextArea textArea = new TextArea(exceptionText);
101+
textArea.setEditable(false);
102+
textArea.setWrapText(true);
103+
104+
textArea.setMaxWidth(Double.MAX_VALUE);
105+
textArea.setMaxHeight(Double.MAX_VALUE);
106+
GridPane.setVgrow(textArea, Priority.ALWAYS);
107+
GridPane.setHgrow(textArea, Priority.ALWAYS);
108+
109+
GridPane expContent = new GridPane();
110+
expContent.setMaxWidth(Double.MAX_VALUE);
111+
expContent.add(label, 0, 0);
112+
expContent.add(textArea, 0, 1);
113+
114+
alert.getDialogPane().setExpandableContent(expContent);
115+
alert.showAndWait();
37116
}
38117

39118
public void clickedDisconnect(MouseEvent mouseEvent) {
@@ -48,13 +127,23 @@ public void connected() {
48127
connect.setVisible(false);
49128
disconnect.setVisible(true);
50129
address.setDisable(true);
130+
sendContainer.setDisable(false);
131+
receivedContainer.setDisable(false);
132+
}
133+
134+
@Override
135+
public void error(@NotNull Throwable ex) {
136+
showError(ex);
137+
closed();
51138
}
52139

53140
@Override
54141
public void closed() {
55142
connect.setVisible(true);
56143
disconnect.setVisible(false);
57144
address.setDisable(false);
145+
sendContainer.setDisable(true);
146+
receivedContainer.setDisable(true);
58147
}
59148

60149
@Override
@@ -66,14 +155,46 @@ public void clickedGenerateId(MouseEvent mouseEvent) {
66155
jsonrpcId.setText(String.valueOf(ThreadLocalRandom.current().nextInt(1000)));
67156
}
68157

69-
public void clickedSend(MouseEvent mouseEvent) {
158+
public void clickedGeneralSend(MouseEvent mouseEvent) {
159+
if (networkThread == null) return;
160+
161+
networkThread.sendGeneral(jsonrpcId.getText(), jsonrpcMethod.getText(), jsonrpcParams.getText(), this);
162+
}
163+
164+
public void clickedMercurySend(MouseEvent mouseEvent) {
70165
if (networkThread == null) return;
71166

72-
networkThread.send(jsonrpcId.getText(), jsonrpcMethod.getText(), jsonrpcParams.getText(), this);
167+
networkThread.sendMercury(mercuryMethod.getSelectionModel().getSelectedItem(), mercuryUri.getText(),
168+
mercuryContentType.getText(), Header.toMap(mercuryHeaders.getItems()), this);
73169
}
74170

75171
@Override
76172
public void response(@NotNull JsonObject json) {
77173
responses.getItems().add(json.toString());
78174
}
175+
176+
public void clickedMercuryAddEmptyHeader(MouseEvent mouseEvent) {
177+
mercuryHeaders.getItems().add(new Header("", ""));
178+
int last = mercuryHeaders.getItems().size() - 1;
179+
mercuryHeaders.getSelectionModel().select(last);
180+
mercuryHeaders.getFocusModel().focus(last);
181+
}
182+
183+
public static class Header {
184+
private final SimpleStringProperty key;
185+
private final SimpleStringProperty value;
186+
187+
Header(String key, String value) {
188+
this.key = new SimpleStringProperty(null, "key", key);
189+
this.value = new SimpleStringProperty(null, "value", value);
190+
}
191+
192+
@NotNull
193+
static Map<String, String> toMap(List<Header> list) {
194+
Map<String, String> map = new HashMap<>();
195+
for (Header header : list) map.put(header.key.getValue(), header.value.getValue());
196+
return map;
197+
}
198+
}
199+
79200
}

api-client/src/main/java/xyz.gianlu.librespot.api.client/NetworkThread.java

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package xyz.gianlu.librespot.api.client;
22

3+
import com.google.gson.JsonArray;
34
import com.google.gson.JsonObject;
45
import com.google.gson.JsonParser;
6+
import javafx.application.Platform;
57
import org.java_websocket.client.WebSocketClient;
68
import org.java_websocket.handshake.ServerHandshake;
79
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
811

912
import java.net.URI;
1013
import java.util.HashMap;
1114
import java.util.Map;
15+
import java.util.concurrent.ThreadLocalRandom;
1216

1317
/**
1418
* @author Gianlu
@@ -19,13 +23,25 @@ public class NetworkThread extends Thread {
1923
private final Listener listener;
2024
private final Map<String, Callback> requests = new HashMap<>();
2125

22-
public NetworkThread(@NotNull String uri, @NotNull Listener listener) {
23-
client = new Client(URI.create(uri));
26+
NetworkThread(@NotNull URI uri, @NotNull Listener listener) {
27+
client = new Client(uri);
2428
this.listener = listener;
2529
start();
2630
}
2731

28-
public void close() {
32+
private static void addParams(@NotNull JsonObject obj, @Nullable String params) {
33+
if (params == null) return;
34+
35+
if (params.isEmpty()) {
36+
obj.addProperty("params", "");
37+
} else if ((params.startsWith("{") && params.endsWith("}")) || (params.startsWith("[") && params.endsWith("]"))) {
38+
obj.add("params", PARSER.parse(params));
39+
} else {
40+
obj.addProperty("params", params);
41+
}
42+
}
43+
44+
void close() {
2945
client.close();
3046
}
3147

@@ -38,12 +54,43 @@ public void run() {
3854
}
3955
}
4056

41-
public void send(String id, String method, String params, @NotNull Callback listener) {
57+
void sendGeneral(@NotNull String id, @NotNull String method, @Nullable String params, @NotNull Callback listener) {
4258
JsonObject obj = new JsonObject();
4359
obj.addProperty("jsonrpc", "2.0");
4460
obj.addProperty("id", id);
4561
obj.addProperty("method", method);
46-
if (!params.isEmpty()) obj.add("params", PARSER.parse(params));
62+
addParams(obj, params);
63+
64+
client.send(obj.toString());
65+
requests.put(id, listener);
66+
}
67+
68+
void sendMercury(@NotNull String method, @NotNull String uri, @Nullable String contentType, @NotNull Map<String, String> headers, @NotNull Callback listener) {
69+
String id = String.valueOf(ThreadLocalRandom.current().nextInt(1000));
70+
71+
JsonObject obj = new JsonObject();
72+
obj.addProperty("jsonrpc", "2.0");
73+
obj.addProperty("id", id);
74+
obj.addProperty("method", "mercury.request");
75+
76+
JsonObject params = new JsonObject();
77+
obj.add("params", params);
78+
79+
params.addProperty("method", method);
80+
params.addProperty("uri", uri);
81+
if (contentType != null && !contentType.isEmpty())
82+
params.addProperty("contentType", contentType);
83+
84+
if (!headers.isEmpty()) {
85+
JsonArray array = new JsonArray(headers.size());
86+
params.add("headers", array);
87+
for (Map.Entry<String, String> entry : headers.entrySet()) {
88+
JsonObject e = new JsonObject();
89+
e.addProperty("key", entry.getKey());
90+
e.addProperty("value", entry.getValue());
91+
array.add(e);
92+
}
93+
}
4794

4895
client.send(obj.toString());
4996
requests.put(id, listener);
@@ -56,6 +103,8 @@ public interface Callback {
56103
public interface Listener {
57104
void connected();
58105

106+
void error(@NotNull Throwable ex);
107+
59108
void closed();
60109

61110
void unknownResponse(@NotNull JsonObject obj);
@@ -68,26 +117,26 @@ private class Client extends WebSocketClient {
68117

69118
@Override
70119
public void onOpen(ServerHandshake serverHandshake) {
71-
listener.connected();
120+
Platform.runLater(listener::connected);
72121
}
73122

74123
@Override
75124
public void onMessage(String s) {
76125
JsonObject obj = PARSER.parse(s).getAsJsonObject();
77126
String id = obj.get("id").getAsString();
78-
Callback callback = requests.get(id);
79-
if (callback == null) listener.unknownResponse(obj);
80-
else callback.response(obj);
127+
Callback callback = requests.remove(id);
128+
if (callback == null) Platform.runLater(() -> listener.unknownResponse(obj));
129+
else Platform.runLater(() -> callback.response(obj));
81130
}
82131

83132
@Override
84133
public void onClose(int i, String s, boolean b) {
85-
listener.closed();
134+
Platform.runLater(listener::closed);
86135
}
87136

88137
@Override
89-
public void onError(Exception e) {
90-
e.printStackTrace();
138+
public void onError(Exception ex) {
139+
Platform.runLater(() -> listener.error(ex));
91140
}
92141
}
93142
}

0 commit comments

Comments
 (0)