Skip to content

Commit ba57603

Browse files
authored
Merge pull request #298 from librespot-org/feature/instance-api-endpoints
Added instance termination and session close endpoints
2 parents 1c9537a + 9fbe643 commit ba57603

7 files changed

Lines changed: 139 additions & 5 deletions

File tree

api/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ All the endpoints will respond with `200` if successful or:
3939
- `GET /profile/{user_id}/followers` Retrieve a list of profiles that are followers of the specified user
4040
- `GET /profile/{user_id}/following` Retrieve a list of profiles that the specified user is following
4141

42+
### Instance
43+
- `POST /instance/terminate` Terminates the API server.
44+
- `POST /instance/close` Closes the current session (and player).
45+
4246
### Events
4347
You can subscribe for players events by creating a WebSocket connection to `/events`.
4448
The currently available events are:

api/src/main/java/xyz/gianlu/librespot/api/ApiServer.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,23 @@ public class ApiServer {
1313
private static final Logger LOGGER = LogManager.getLogger(ApiServer.class);
1414
protected final RoutingHandler handler;
1515
protected final EventsHandler events = new EventsHandler();
16+
private final SessionWrapper wrapper;
1617
private final int port;
1718
private final String host;
1819
private Undertow undertow = null;
1920

2021
public ApiServer(int port, @NotNull String host, @NotNull SessionWrapper wrapper) {
2122
this.port = port;
2223
this.host = host;
24+
this.wrapper = wrapper;
2325
this.handler = new RoutingHandler()
2426
.post("/metadata/{type}/{uri}", new MetadataHandler(wrapper, true))
2527
.post("/metadata/{uri}", new MetadataHandler(wrapper, false))
2628
.post("/search/{query}", new SearchHandler(wrapper))
2729
.post("/token/{scope}", new TokensHandler(wrapper))
2830
.post("/profile/{user_id}/{action}", new ProfileHandler(wrapper))
2931
.post("/web-api/{endpoint}", new WebApiHandler(wrapper))
32+
.post("/instance/{action}", InstanceHandler.forSession(this, wrapper))
3033
.get("/events", events)
3134
.setFallbackHandler(new PathHandler(ResponseCodeHandler.HANDLE_404)
3235
.addPrefixPath("/web-api", new WebApiHandler(wrapper)));
@@ -43,11 +46,13 @@ public void start() {
4346
}
4447

4548
public void stop() {
49+
wrapper.clear();
50+
4651
if (undertow != null) {
4752
undertow.stop();
4853
undertow = null;
49-
}
5054

51-
LOGGER.info("Server stopped!");
55+
LOGGER.info("Server stopped!");
56+
}
5257
}
5358
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package xyz.gianlu.librespot.api;
22

33
import org.jetbrains.annotations.NotNull;
4+
import xyz.gianlu.librespot.api.handlers.InstanceHandler;
45
import xyz.gianlu.librespot.api.handlers.PlayerHandler;
56

67
/**
78
* @author devgianlu
89
*/
910
public class PlayerApiServer extends ApiServer {
11+
private final PlayerWrapper wrapper;
12+
1013
public PlayerApiServer(int port, @NotNull String host, @NotNull PlayerWrapper wrapper) {
1114
super(port, host, wrapper);
15+
this.wrapper = wrapper;
1216

1317
handler.post("/player/{cmd}", new PlayerHandler(wrapper));
18+
handler.post("/instance/{action}", InstanceHandler.forPlayer(this, wrapper)); // Overrides session only handler
1419
wrapper.setListener(events);
1520
}
1621
}

api/src/main/java/xyz/gianlu/librespot/api/PlayerWrapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ protected void set(@NotNull Session session) {
8181

8282
@Override
8383
protected void clear() {
84-
super.clear();
85-
8684
Player old = playerRef.get();
8785
if (old != null) old.close();
8886
playerRef.set(null);
8987

9088
if (listener != null && old != null) listener.onPlayerCleared(old);
89+
90+
super.clear();
9191
}
9292

9393
@Nullable

api/src/main/java/xyz/gianlu/librespot/api/SessionWrapper.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import xyz.gianlu.librespot.ZeroconfServer;
66
import xyz.gianlu.librespot.core.Session;
77

8+
import java.io.IOException;
89
import java.util.concurrent.atomic.AtomicReference;
910

1011
/**
@@ -70,7 +71,14 @@ protected void set(@NotNull Session session) {
7071
protected void clear() {
7172
Session old = sessionRef.get();
7273
sessionRef.set(null);
73-
if (listener != null && old != null) listener.onSessionCleared(old);
74+
if (old != null) {
75+
try {
76+
old.close();
77+
} catch (IOException ignored) {
78+
}
79+
80+
if (listener != null) listener.onSessionCleared(old);
81+
}
7482
}
7583

7684
@Nullable
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package xyz.gianlu.librespot.api.handlers;
2+
3+
import io.undertow.server.HttpServerExchange;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
6+
import xyz.gianlu.librespot.api.ApiServer;
7+
import xyz.gianlu.librespot.api.PlayerWrapper;
8+
import xyz.gianlu.librespot.api.SessionWrapper;
9+
import xyz.gianlu.librespot.api.Utils;
10+
import xyz.gianlu.librespot.core.Session;
11+
import xyz.gianlu.librespot.player.Player;
12+
13+
import java.io.IOException;
14+
import java.util.Deque;
15+
import java.util.Map;
16+
17+
/**
18+
* @author devgianlu
19+
*/
20+
public final class InstanceHandler {
21+
22+
public static AbsSessionHandler forSession(@NotNull ApiServer server, @NotNull SessionWrapper wrapper) {
23+
return new SessionHandler(server, wrapper);
24+
}
25+
26+
public static AbsPlayerHandler forPlayer(@NotNull ApiServer server, @NotNull PlayerWrapper wrapper) {
27+
return new PlayerHandler(server, wrapper);
28+
}
29+
30+
@Nullable
31+
private static String getAction(@NotNull HttpServerExchange exchange) throws IOException {
32+
Map<String, Deque<String>> params = Utils.readParameters(exchange);
33+
String action = Utils.getFirstString(params, "action");
34+
if (action == null) {
35+
Utils.invalidParameter(exchange, "action");
36+
return null;
37+
}
38+
39+
return action;
40+
}
41+
42+
private static class SessionHandler extends AbsSessionHandler {
43+
private final ApiServer server;
44+
45+
SessionHandler(ApiServer server, @NotNull SessionWrapper wrapper) {
46+
super(wrapper);
47+
this.server = server;
48+
}
49+
50+
@Override
51+
protected void handleRequest(@NotNull HttpServerExchange exchange, @NotNull Session session) throws Exception {
52+
exchange.startBlocking();
53+
if (exchange.isInIoThread()) {
54+
exchange.dispatch(this);
55+
return;
56+
}
57+
58+
String action = getAction(exchange);
59+
if (action == null) return;
60+
61+
switch (action) {
62+
case "terminate":
63+
exchange.endExchange();
64+
new Thread(server::stop).start();
65+
break;
66+
case "close":
67+
session.close();
68+
break;
69+
default:
70+
Utils.invalidParameter(exchange, "action");
71+
break;
72+
}
73+
}
74+
}
75+
76+
private static class PlayerHandler extends AbsPlayerHandler {
77+
private final ApiServer server;
78+
79+
PlayerHandler(@NotNull ApiServer server, @NotNull PlayerWrapper wrapper) {
80+
super(wrapper);
81+
this.server = server;
82+
}
83+
84+
@Override
85+
protected void handleRequest(@NotNull HttpServerExchange exchange, @NotNull Session session, @NotNull Player player) throws Exception {
86+
exchange.startBlocking();
87+
if (exchange.isInIoThread()) {
88+
exchange.dispatch(this);
89+
return;
90+
}
91+
92+
String action = getAction(exchange);
93+
if (action == null) return;
94+
95+
switch (action) {
96+
case "terminate":
97+
exchange.endExchange();
98+
new Thread(server::stop).start();
99+
break;
100+
case "close":
101+
player.close();
102+
session.close();
103+
break;
104+
default:
105+
Utils.invalidParameter(exchange, "action");
106+
break;
107+
}
108+
}
109+
}
110+
}

player/src/main/java/xyz/gianlu/librespot/player/Player.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,8 @@ public void close() {
829829

830830
scheduler.shutdown();
831831
events.close();
832+
833+
LOGGER.info("Closed player.");
832834
}
833835

834836
public interface EventsListener {

0 commit comments

Comments
 (0)