Skip to content

Commit 3604b4e

Browse files
committed
Correctly close session
1 parent 0388c4f commit 3604b4e

7 files changed

Lines changed: 106 additions & 31 deletions

File tree

core/src/main/java/xyz/gianlu/librespot/Main.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
*/
1414
public class Main {
1515

16-
public static void main(String[] args) throws IOException, GeneralSecurityException, Session.SpotifyAuthenticationException, SpotifyIrc.IrcException, MercuryClient.PubSubException {
17-
new Session.Builder(new FileConfiguration(new File("conf.properties"), args)).create();
16+
public static void main(String[] args) throws IOException, GeneralSecurityException, Session.SpotifyAuthenticationException, SpotifyIrc.IrcException, MercuryClient.PubSubException, InterruptedException {
17+
Session session = new Session.Builder(new FileConfiguration(new File("conf.properties"), args)).create();
18+
19+
Thread.sleep(8000);
20+
21+
session.close();
1822
}
1923
}

core/src/main/java/xyz/gianlu/librespot/core/Session.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@
2222
import javax.crypto.Cipher;
2323
import javax.crypto.Mac;
2424
import javax.crypto.spec.SecretKeySpec;
25-
import java.io.ByteArrayOutputStream;
26-
import java.io.DataInputStream;
27-
import java.io.DataOutputStream;
28-
import java.io.IOException;
25+
import java.io.*;
2926
import java.net.Socket;
3027
import java.net.SocketTimeoutException;
3128
import java.nio.ByteBuffer;
@@ -43,7 +40,7 @@
4340
/**
4441
* @author Gianlu
4542
*/
46-
public class Session implements AutoCloseable {
43+
public class Session implements Closeable {
4744
private static final Logger LOGGER = Logger.getLogger(Session.class);
4845
private final DiffieHellman keys;
4946
private final Socket socket;
@@ -235,23 +232,28 @@ private void authenticate(@NotNull Authentication.LoginCredentials credentials)
235232
}
236233

237234
@Override
238-
public void close() throws Exception {
239-
receiver.stop();
240-
receiver = null;
241-
242-
socket.close();
243-
244-
mercuryClient.close();
245-
mercuryClient = null;
235+
public void close() throws IOException {
236+
player.close();
237+
player = null;
246238

247239
audioKeyManager.close();
248240
audioKeyManager = null;
249241

250242
channelManager.close();
251243
channelManager = null;
252244

253-
player = null;
245+
spirc.close();
254246
spirc = null;
247+
248+
mercuryClient.close();
249+
mercuryClient = null;
250+
251+
receiver.stop();
252+
receiver = null;
253+
254+
executorService.shutdown();
255+
socket.close();
256+
255257
apWelcome = null;
256258
cipherPair = null;
257259

@@ -547,7 +549,7 @@ public void run() {
547549
continue;
548550
}
549551
} catch (IOException | GeneralSecurityException ex) {
550-
LOGGER.fatal("Failed reading packet!", ex);
552+
if (!shouldStop) LOGGER.fatal("Failed reading packet!", ex);
551553
return;
552554
}
553555

core/src/main/java/xyz/gianlu/librespot/mercury/MercuryClient.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ public MercuryClient(@NotNull Session session) {
3636
super(session);
3737
}
3838

39-
@NotNull
40-
public String username() {
41-
return session.apWelcome().getCanonicalUsername();
42-
}
43-
4439
public void subscribe(@NotNull String uri, @NotNull SubListener listener) throws IOException, PubSubException {
4540
Response response = sendSync(RawMercuryRequest.sub(uri));
4641
if (response.statusCode != 200) throw new PubSubException(response);
@@ -57,6 +52,14 @@ public void subscribe(@NotNull String uri, @NotNull SubListener listener) throws
5752
LOGGER.trace(String.format("Subscribed successfully to %s!", uri));
5853
}
5954

55+
public void unsubscribe(@NotNull String uri) throws IOException, PubSubException {
56+
Response response = sendSync(RawMercuryRequest.unsub(uri));
57+
if (response.statusCode != 200) throw new PubSubException(response);
58+
59+
subscriptions.removeIf(l -> l.matches(uri));
60+
LOGGER.trace(String.format("Unsubscribed successfully from %s!", uri));
61+
}
62+
6063
@NotNull
6164
public Response sendSync(@NotNull RawMercuryRequest request) throws IOException {
6265
AtomicReference<Response> reference = new AtomicReference<>(null);
@@ -168,7 +171,7 @@ protected void handle(@NotNull Packet packet) throws InvalidProtocolBufferExcept
168171

169172
if (!dispatched)
170173
LOGGER.warn(String.format("Couldn't dispatch Mercury sub event, seq: %d, uri: %s, code %d", seq, header.getUri(), header.getStatusCode()));
171-
} else if (packet.is(Packet.Type.MercuryReq) || packet.is(Packet.Type.MercurySub)) {
174+
} else if (packet.is(Packet.Type.MercuryReq) || packet.is(Packet.Type.MercurySub) || packet.is(Packet.Type.MercuryUnsub)) {
172175
Callback callback = callbacks.remove(seq);
173176
if (callback != null) {
174177
callback.response(resp);

core/src/main/java/xyz/gianlu/librespot/mercury/RawMercuryRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ public static RawMercuryRequest sub(@NotNull String uri) {
2323
return RawMercuryRequest.newBuilder().setUri(uri).setMethod("SUB").build();
2424
}
2525

26+
@NotNull
27+
public static RawMercuryRequest unsub(@NotNull String uri) {
28+
return RawMercuryRequest.newBuilder().setUri(uri).setMethod("UNSUB").build();
29+
}
30+
2631
@NotNull
2732
public static RawMercuryRequest get(@NotNull String uri) {
2833
return RawMercuryRequest.newBuilder().setUri(uri).setMethod("GET").build();

core/src/main/java/xyz/gianlu/librespot/player/CacheManager.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
/**
2121
* @author Gianlu
2222
*/
23-
public class CacheManager {
23+
public class CacheManager implements Closeable {
2424
static final byte BYTE_CREATED_AT = 0b1111111;
2525
private static final Logger LOGGER = Logger.getLogger(CacheManager.class);
2626
private static final long CLEAN_UP_THRESHOLD = TimeUnit.DAYS.toMillis(7);
@@ -29,6 +29,7 @@ public class CacheManager {
2929
private final Map<String, Handler> loadedHandlers;
3030
private final ControlTable controlTable;
3131
private final ExecutorService executorService = Executors.newCachedThreadPool(new NameThreadFactory(r -> "cache-io-" + r.hashCode()));
32+
private volatile boolean closed = false;
3233

3334
public CacheManager(@NotNull CacheConfiguration conf) throws IOException {
3435
this.enabled = conf.cacheEnabled();
@@ -49,6 +50,7 @@ public CacheManager(@NotNull CacheConfiguration conf) throws IOException {
4950

5051
@Nullable
5152
Handler handler(@NotNull ByteString fileId) {
53+
if (closed) throw new IllegalStateException("CacheManager is closed!");
5254
if (!enabled) return null;
5355

5456
String hexId = Utils.bytesToHex(fileId);
@@ -62,6 +64,21 @@ Handler handler(@NotNull ByteString fileId) {
6264
});
6365
}
6466

67+
@Override
68+
public void close() throws IOException {
69+
closed = true;
70+
executorService.shutdown();
71+
72+
if (controlTable != null) controlTable.safeSave();
73+
74+
if (loadedHandlers != null) {
75+
for (Handler handler : loadedHandlers.values())
76+
handler.close();
77+
78+
loadedHandlers.clear();
79+
}
80+
}
81+
6582
public interface CacheConfiguration {
6683
boolean cacheEnabled();
6784

@@ -134,6 +151,8 @@ private void safeSave() {
134151
}
135152

136153
void writtenChunk(@NotNull String fileId, int index) {
154+
if (closed) throw new IllegalStateException("CacheManager is closed!");
155+
137156
for (CacheEntry entry : entries) {
138157
if (fileId.equals(entry.hexId))
139158
entry.writtenChunk(index);
@@ -143,10 +162,14 @@ void writtenChunk(@NotNull String fileId, int index) {
143162
}
144163

145164
void writeHeaders(@NotNull String fileId, byte[] headersId, byte[][] headersData, short chunksCount) {
165+
if (closed) throw new IllegalStateException("CacheManager is closed!");
166+
146167
entries.add(new CacheEntry(fileId, headersId, headersData, chunksCount));
147168
}
148169

149170
public void remove(@NotNull String fileId) {
171+
if (closed) throw new IllegalStateException("CacheManager is closed!");
172+
150173
Iterator<CacheEntry> iterator = entries.iterator();
151174
while (iterator.hasNext()) {
152175
CacheEntry entry = iterator.next();
@@ -160,6 +183,8 @@ public void remove(@NotNull String fileId) {
160183
}
161184

162185
void requestHeaders(@NotNull String fileId, @NotNull AudioFile file) {
186+
if (closed) throw new IllegalStateException("CacheManager is closed!");
187+
163188
for (CacheEntry entry : entries) {
164189
if (fileId.equals(entry.hexId)) {
165190
entry.requestHeaders(file);
@@ -286,6 +311,7 @@ private Handler(@NotNull String fileId) throws IOException {
286311
}
287312

288313
boolean has(int chunk) {
314+
if (closed) throw new IllegalStateException("CacheManager is closed!");
289315
return controlTable.has(fileId, chunk);
290316
}
291317

@@ -295,10 +321,12 @@ public void close() throws IOException {
295321
}
296322

297323
void requestHeaders(@NotNull AudioFile fetch) {
324+
if (closed) throw new IllegalStateException("CacheManager is closed!");
298325
executorService.execute(() -> controlTable.requestHeaders(fileId, fetch));
299326
}
300327

301328
void requestChunk(int index, @NotNull AudioFile file) {
329+
if (closed) throw new IllegalStateException("CacheManager is closed!");
302330
executorService.execute(() -> {
303331
try {
304332
cache.seek(index * CHUNK_SIZE);
@@ -314,20 +342,24 @@ void requestChunk(int index, @NotNull AudioFile file) {
314342
}
315343

316344
public void write(byte[] buffer, int index) throws IOException {
345+
if (closed) throw new IllegalStateException("CacheManager is closed!");
317346
cache.seek(index * CHUNK_SIZE);
318347
cache.write(buffer);
319348
controlTable.writtenChunk(fileId, index);
320349
}
321350

322351
public void remove() {
352+
if (closed) throw new IllegalStateException("CacheManager is closed!");
323353
controlTable.remove(fileId);
324354
}
325355

326356
void writeHeaders(byte[] headersId, byte[][] headersData, short chunksCount) {
357+
if (closed) throw new IllegalStateException("CacheManager is closed!");
327358
controlTable.writeHeaders(fileId, headersId, headersData, chunksCount);
328359
}
329360

330361
boolean hasHeaders() {
362+
if (closed) throw new IllegalStateException("CacheManager is closed!");
331363
return controlTable.hasHeaders(fileId);
332364
}
333365
}

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
import xyz.gianlu.librespot.spirc.FrameListener;
1313
import xyz.gianlu.librespot.spirc.SpotifyIrc;
1414

15+
import java.io.Closeable;
1516
import java.io.IOException;
1617

1718
/**
1819
* @author Gianlu
1920
*/
20-
public class Player implements FrameListener, TrackHandler.Listener {
21+
public class Player implements FrameListener, TrackHandler.Listener, Closeable {
2122
private static final Logger LOGGER = Logger.getLogger(Player.class);
2223
private final Session session;
2324
private final SpotifyIrc spirc;
@@ -355,6 +356,21 @@ private void handlePrev() {
355356
}
356357
}
357358

359+
@Override
360+
public void close() throws IOException {
361+
if (trackHandler != null) {
362+
trackHandler.close();
363+
trackHandler = null;
364+
}
365+
366+
if (preloadTrackHandler != null) {
367+
preloadTrackHandler.close();
368+
preloadTrackHandler = null;
369+
}
370+
371+
cacheManager.close();
372+
}
373+
358374
public interface Configuration {
359375
@NotNull
360376
StreamFeeder.AudioQuality preferredQuality();

core/src/main/java/xyz/gianlu/librespot/spirc/SpotifyIrc.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
import org.jetbrains.annotations.NotNull;
66
import org.jetbrains.annotations.Nullable;
77
import xyz.gianlu.librespot.Version;
8+
import xyz.gianlu.librespot.common.proto.Spirc;
89
import xyz.gianlu.librespot.core.Session;
910
import xyz.gianlu.librespot.mercury.MercuryClient;
1011
import xyz.gianlu.librespot.mercury.RawMercuryRequest;
1112
import xyz.gianlu.librespot.mercury.SubListener;
1213
import xyz.gianlu.librespot.player.PlayerRunner;
13-
import xyz.gianlu.librespot.common.proto.Spirc;
1414

15+
import java.io.Closeable;
1516
import java.io.IOException;
1617
import java.util.HashSet;
1718
import java.util.Set;
@@ -20,7 +21,7 @@
2021
/**
2122
* @author Gianlu
2223
*/
23-
public class SpotifyIrc {
24+
public class SpotifyIrc implements Closeable {
2425
private static final Logger LOGGER = Logger.getLogger(SpotifyIrc.class);
2526
private final AtomicInteger seqHolder = new AtomicInteger(1);
2627
private final String uri;
@@ -115,10 +116,6 @@ public synchronized void send(@NotNull Spirc.MessageType type, @NotNull Spirc.Fr
115116
}
116117
}
117118

118-
private void sendNotify() throws IOException, IrcException {
119-
sendNotify(null, null);
120-
}
121-
122119
private void sendNotify(@Nullable String recipient, @Nullable Spirc.Frame.Builder frame) throws IOException, IrcException {
123120
if (frame == null) frame = Spirc.Frame.newBuilder();
124121

@@ -153,6 +150,18 @@ public void deviceStateUpdated(@NotNull Spirc.State.Builder state) {
153150
}
154151
}
155152

153+
@Override
154+
public void close() throws IOException {
155+
try {
156+
send(Spirc.MessageType.kMessageTypeGoodbye);
157+
session.mercury().unsubscribe(uri);
158+
} catch (SpotifyIrc.IrcException | MercuryClient.PubSubException ex) {
159+
throw new IOException(ex);
160+
}
161+
162+
internalListener.clear();
163+
}
164+
156165
public static class IrcException extends Exception {
157166
private IrcException(MercuryClient.Response response) {
158167
super(String.format("status: %d", response.statusCode));
@@ -162,6 +171,10 @@ private IrcException(MercuryClient.Response response) {
162171
private final class SpircListener implements SubListener {
163172
private final Set<FrameListener> listeners = new HashSet<>();
164173

174+
void clear() {
175+
listeners.clear();
176+
}
177+
165178
@Override
166179
public final void event(MercuryClient.@NotNull Response resp) {
167180
String ident = session.deviceId();

0 commit comments

Comments
 (0)