Skip to content

Commit 3f479e0

Browse files
committed
Early version of the reconnect feature.
This will probably be not be the final version, but should be is functional as of now. TS3Config.setLoginCredentials had to be removed. Use TS3(Async)Api.login instead. I might add it in again at a later time to restore backwards-compatibility. There has also been quite a bit of internal refactoring. If you've directly been accessing the IO part of the API, your code will break. If you absolutely need to have access to these parts of the API, let me know. Else the IO part will stay hidden from the user.
1 parent 424f7ef commit 3f479e0

File tree

12 files changed

+457
-193
lines changed

12 files changed

+457
-193
lines changed

src/main/java/com/github/theholywaffle/teamspeak3/KeepAliveThread.java

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,41 +26,37 @@
2626
* #L%
2727
*/
2828

29-
import com.github.theholywaffle.teamspeak3.commands.CWhoAmI;
29+
import java.util.logging.Level;
3030

3131
public class KeepAliveThread extends Thread {
3232

3333
private static final int SLEEP = 60_000;
3434

35-
private final TS3Query ts3;
3635
private final SocketWriter writer;
36+
private final TS3ApiAsync asyncApi;
3737

38-
public KeepAliveThread(TS3Query ts3, SocketWriter socketWriter) {
38+
public KeepAliveThread(SocketWriter writer, TS3ApiAsync asyncApi) {
3939
super("[TeamSpeak-3-Java-API] Keep alive");
40-
this.ts3 = ts3;
41-
this.writer = socketWriter;
40+
this.writer = writer;
41+
this.asyncApi = asyncApi;
4242
}
4343

4444
@Override
4545
public void run() {
46-
while (ts3.getSocket() != null && ts3.getSocket().isConnected()
47-
&& ts3.getOut() != null && !isInterrupted()) {
48-
final long idleTime = writer.getIdleTime();
49-
if (idleTime >= SLEEP) {
50-
ts3.doCommand(new CWhoAmI());
51-
continue;
46+
try {
47+
while (!isInterrupted()) {
48+
final long idleTime = writer.getIdleTime();
49+
if (idleTime >= SLEEP) {
50+
// Using the asynchronous API so we get InterruptedExceptions
51+
asyncApi.whoAmI().get();
52+
} else {
53+
Thread.sleep(SLEEP - idleTime);
54+
}
5255
}
53-
try {
54-
Thread.sleep(SLEEP - idleTime);
55-
} catch (final InterruptedException e) {
56-
interrupt();
57-
break;
58-
}
59-
}
60-
61-
if (!isInterrupted()) {
62-
TS3Query.log.warning("KeepAlive thread has stopped!");
56+
} catch (final InterruptedException e) {
57+
// Thread stopped properly, ignore
58+
} catch (final Exception e) {
59+
TS3Query.log.log(Level.WARNING, "KeepAlive thread has stopped!", e);
6360
}
6461
}
65-
6662
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package com.github.theholywaffle.teamspeak3;
2+
3+
/*
4+
* #%L
5+
* TeamSpeak 3 Java API
6+
* %%
7+
* Copyright (C) 2015 Bert De Geyter
8+
* %%
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy
10+
* of this software and associated documentation files (the "Software"), to deal
11+
* in the Software without restriction, including without limitation the rights
12+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
* copies of the Software, and to permit persons to whom the Software is
14+
* furnished to do so, subject to the following conditions:
15+
*
16+
* The above copyright notice and this permission notice shall be included in
17+
* all copies or substantial portions of the Software.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
* #L%
27+
*/
28+
29+
import com.github.theholywaffle.teamspeak3.api.Callback;
30+
import com.github.theholywaffle.teamspeak3.api.exception.TS3ConnectionFailedException;
31+
import com.github.theholywaffle.teamspeak3.commands.Command;
32+
33+
import java.io.IOException;
34+
import java.net.Socket;
35+
import java.util.Collections;
36+
import java.util.LinkedHashMap;
37+
import java.util.Map;
38+
import java.util.Queue;
39+
import java.util.concurrent.ConcurrentLinkedQueue;
40+
41+
public class QueryIO {
42+
43+
private final Socket socket;
44+
private final SocketReader socketReader;
45+
private final SocketWriter socketWriter;
46+
private final KeepAliveThread keepAlive;
47+
48+
private final Queue<Command> commandQueue;
49+
private final Map<Command, Callback> callbackMap;
50+
51+
QueryIO(TS3Query query, TS3Config config) {
52+
commandQueue = new ConcurrentLinkedQueue<>();
53+
callbackMap = Collections.synchronizedMap(new LinkedHashMap<Command, Callback>(16));
54+
55+
Socket tmpSocket = null;
56+
try {
57+
tmpSocket = new Socket(config.getHost(), config.getQueryPort());
58+
socket = tmpSocket;
59+
socketReader = new SocketReader(query, this);
60+
socketWriter = new SocketWriter(this, config.getFloodRate().getMs());
61+
keepAlive = new KeepAliveThread(socketWriter, query.getAsyncApi());
62+
} catch (IOException e) {
63+
// Clean up resources and fail
64+
if (tmpSocket != null) {
65+
try {
66+
tmpSocket.close();
67+
} catch (IOException ignored) {
68+
}
69+
}
70+
71+
throw new TS3ConnectionFailedException(e);
72+
}
73+
74+
// From here on: all resources have been initialized and are non-null
75+
socketReader.start();
76+
socketWriter.start();
77+
keepAlive.start();
78+
}
79+
80+
public void continueFrom(QueryIO io) {
81+
if (io == null || io.commandQueue.isEmpty()) return;
82+
83+
callbackMap.putAll(io.callbackMap);
84+
Command lastSent = io.commandQueue.poll();
85+
commandQueue.add(lastSent.reset());
86+
commandQueue.addAll(io.commandQueue);
87+
88+
io.commandQueue.clear();
89+
io.callbackMap.clear();
90+
}
91+
92+
public void disconnect() {
93+
keepAlive.interrupt();
94+
socketWriter.interrupt();
95+
socketReader.interrupt();
96+
97+
try {
98+
keepAlive.join();
99+
socketWriter.join();
100+
socketReader.join();
101+
} catch (final InterruptedException e) {
102+
// Restore the interrupt for the caller
103+
Thread.currentThread().interrupt();
104+
}
105+
106+
try {
107+
socket.close();
108+
} catch (IOException ignored) {
109+
}
110+
}
111+
112+
public void queueCommand(Command command, Callback callback) {
113+
if (command == null) throw new NullPointerException("Command cannot be null!");
114+
115+
if (callback != null) {
116+
callbackMap.put(command, callback);
117+
}
118+
commandQueue.add(command);
119+
}
120+
121+
public Socket getSocket() {
122+
return socket;
123+
}
124+
125+
public Map<Command, Callback> getCallbackMap() {
126+
return callbackMap;
127+
}
128+
129+
public Queue<Command> getCommandQueue() {
130+
return commandQueue;
131+
}
132+
}

src/main/java/com/github/theholywaffle/teamspeak3/SocketReader.java

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,33 @@
2929
import com.github.theholywaffle.teamspeak3.api.Callback;
3030
import com.github.theholywaffle.teamspeak3.commands.Command;
3131

32+
import java.io.BufferedReader;
3233
import java.io.IOException;
33-
import java.util.Collections;
34+
import java.io.InputStreamReader;
3435
import java.util.Iterator;
35-
import java.util.LinkedHashMap;
3636
import java.util.Map;
3737
import java.util.Set;
38-
import java.util.concurrent.ExecutorService;
39-
import java.util.concurrent.Executors;
4038
import java.util.logging.Level;
4139

4240
public class SocketReader extends Thread {
4341

4442
private final TS3Query ts3;
45-
private final ExecutorService userThreadPool;
46-
private final Map<Command, Callback> callbackMap;
43+
private final QueryIO io;
44+
private final BufferedReader in;
4745

48-
private String lastEvent;
46+
private String lastEvent = "";
4947

50-
public SocketReader(TS3Query ts3) {
48+
public SocketReader(TS3Query ts3Query, QueryIO io) throws IOException {
5149
super("[TeamSpeak-3-Java-API] SocketReader");
52-
this.ts3 = ts3;
53-
this.userThreadPool = Executors.newCachedThreadPool();
54-
this.callbackMap = Collections.synchronizedMap(new LinkedHashMap<Command, Callback>());
55-
this.lastEvent = "";
50+
this.io = io;
51+
this.ts3 = ts3Query;
5652

53+
// Connect
54+
this.in = new BufferedReader(new InputStreamReader(io.getSocket().getInputStream(), "UTF-8"));
5755
try {
5856
int i = 0;
59-
while (i < 4 || ts3.getIn().ready()) {
60-
TS3Query.log.info("< " + ts3.getIn().readLine());
57+
while (i < 4 || in.ready()) {
58+
TS3Query.log.info("< " + in.readLine());
6159
i++;
6260
}
6361
} catch (final IOException e) {
@@ -67,13 +65,12 @@ public SocketReader(TS3Query ts3) {
6765

6866
@Override
6967
public void run() {
70-
while (ts3.getSocket() != null && ts3.getSocket().isConnected()
71-
&& ts3.getIn() != null && !isInterrupted()) {
68+
while (!isInterrupted()) {
7269
final String line;
7370

7471
try {
7572
// Will block until a full line of text could be read.
76-
line = ts3.getIn().readLine();
73+
line = in.readLine();
7774
} catch (IOException io) {
7875
if (!isInterrupted()) {
7976
io.printStackTrace();
@@ -87,15 +84,15 @@ public void run() {
8784
continue; // The server is sending garbage
8885
}
8986

90-
final Command c = ts3.getCommandList().peek();
87+
final Command c = io.getCommandQueue().peek();
9188

9289
if (line.startsWith("notify")) {
9390
TS3Query.log.info("< [event] " + line);
9491

9592
// Filter out duplicate events for join, quit and channel move events
9693
if (isDuplicate(line)) continue;
9794

98-
userThreadPool.execute(new Runnable() {
95+
ts3.submitUserTask(new Runnable() {
9996
@Override
10097
public void run() {
10198
final String arr[] = line.split(" ", 2);
@@ -110,7 +107,7 @@ public void run() {
110107
TS3Query.log.severe("TS3 command error: " + c.getError());
111108
}
112109
c.setAnswered();
113-
ts3.getCommandList().remove(c);
110+
io.getCommandQueue().remove(c);
114111
answerCallback(c);
115112
} else {
116113
c.feed(line);
@@ -120,13 +117,20 @@ public void run() {
120117
}
121118
}
122119

123-
userThreadPool.shutdown();
120+
try {
121+
in.close();
122+
} catch (IOException ignored) {
123+
// Ignore
124+
}
125+
124126
if (!isInterrupted()) {
125127
TS3Query.log.warning("SocketReader has stopped!");
128+
ts3.fireDisconnect();
126129
}
127130
}
128131

129132
private void answerCallback(Command c) {
133+
final Map<Command, Callback> callbackMap = io.getCallbackMap();
130134
final Callback callback = callbackMap.get(c);
131135

132136
// Command had no callback registered
@@ -143,7 +147,7 @@ private void answerCallback(Command c) {
143147
}
144148
}
145149

146-
userThreadPool.execute(new Runnable() {
150+
ts3.submitUserTask(new Runnable() {
147151
@Override
148152
public void run() {
149153
try {
@@ -155,10 +159,6 @@ public void run() {
155159
});
156160
}
157161

158-
void registerCallback(Command command, Callback callback) {
159-
callbackMap.put(command, callback);
160-
}
161-
162162
private boolean isDuplicate(String eventMessage) {
163163
if (!(eventMessage.startsWith("notifyclientmoved")
164164
|| eventMessage.startsWith("notifycliententerview")

src/main/java/com/github/theholywaffle/teamspeak3/SocketWriter.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,33 +28,38 @@
2828

2929
import com.github.theholywaffle.teamspeak3.commands.Command;
3030

31+
import java.io.IOException;
32+
import java.io.PrintStream;
33+
3134
public class SocketWriter extends Thread {
3235

33-
private final TS3Query ts3;
36+
private final QueryIO io;
3437
private final int floodRate;
38+
private final PrintStream out;
3539
private volatile long lastCommand = System.currentTimeMillis();
3640

37-
public SocketWriter(TS3Query ts3, int floodRate) {
41+
public SocketWriter(QueryIO io, int floodRate) throws IOException {
3842
super("[TeamSpeak-3-Java-API] SocketWriter");
39-
this.ts3 = ts3;
43+
this.io = io;
4044
if (floodRate > 50) {
4145
this.floodRate = floodRate;
4246
} else {
4347
this.floodRate = 50;
4448
}
49+
50+
out = new PrintStream(io.getSocket().getOutputStream(), true, "UTF-8");
4551
}
4652

4753
@Override
4854
public void run() {
49-
while (ts3.getSocket() != null && ts3.getSocket().isConnected()
50-
&& ts3.getOut() != null && !isInterrupted()) {
51-
final Command c = ts3.getCommandList().peek();
55+
while (!isInterrupted()) {
56+
final Command c = io.getCommandQueue().peek();
5257
if (c != null && !c.isSent()) {
5358
final String msg = c.toString();
5459
TS3Query.log.info("> " + msg);
5560

5661
c.setSent();
57-
ts3.getOut().println(msg);
62+
out.println(msg);
5863
lastCommand = System.currentTimeMillis();
5964
}
6065
try {
@@ -65,6 +70,8 @@ public void run() {
6570
}
6671
}
6772

73+
out.close();
74+
6875
if (!isInterrupted()) {
6976
TS3Query.log.warning("SocketWriter has stopped!");
7077
}
@@ -73,5 +80,4 @@ public void run() {
7380
public long getIdleTime() {
7481
return System.currentTimeMillis() - lastCommand;
7582
}
76-
7783
}

src/main/java/com/github/theholywaffle/teamspeak3/TS3Api.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2543,7 +2543,6 @@ private boolean kickClients(ReasonIdentifier reason, String message, int... clie
25432543
* @return whether the command succeeded or not
25442544
*
25452545
* @querycommands 1
2546-
* @see TS3Config#setLoginCredentials(String, String)
25472546
* @see #logout()
25482547
*/
25492548
public boolean login(String username, String password) {

0 commit comments

Comments
 (0)