001package com.github.theholywaffle.teamspeak3;
002
003/*
004 * #%L
005 * TeamSpeak 3 Java API
006 * %%
007 * Copyright (C) 2015 Bert De Geyter
008 * %%
009 * Permission is hereby granted, free of charge, to any person obtaining a copy
010 * of this software and associated documentation files (the "Software"), to deal
011 * in the Software without restriction, including without limitation the rights
012 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
013 * copies of the Software, and to permit persons to whom the Software is
014 * furnished to do so, subject to the following conditions:
015 * 
016 * The above copyright notice and this permission notice shall be included in
017 * all copies or substantial portions of the Software.
018 * 
019 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
020 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
021 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
022 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
023 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
024 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
025 * THE SOFTWARE.
026 * #L%
027 */
028
029import com.github.theholywaffle.teamspeak3.api.exception.TS3ConnectionFailedException;
030import com.github.theholywaffle.teamspeak3.api.exception.TS3QueryShutDownException;
031import com.github.theholywaffle.teamspeak3.api.reconnect.ConnectionHandler;
032import com.github.theholywaffle.teamspeak3.api.reconnect.DisconnectingConnectionHandler;
033import com.github.theholywaffle.teamspeak3.commands.Command;
034import com.github.theholywaffle.teamspeak3.commands.response.ResponseBuilder;
035
036import java.io.IOException;
037import java.net.Socket;
038import java.util.concurrent.ArrayBlockingQueue;
039import java.util.concurrent.BlockingQueue;
040import java.util.concurrent.LinkedBlockingQueue;
041
042public class QueryIO {
043
044        private final Socket socket;
045        private final SocketReader socketReader;
046        private final SocketWriter socketWriter;
047        private final KeepAliveThread keepAlive;
048
049        private final BlockingQueue<Command> sendQueue;
050        private final BlockingQueue<ResponseBuilder> receiveQueue;
051
052        QueryIO(TS3Query query, TS3Config config) {
053                sendQueue = new LinkedBlockingQueue<>();
054                ConnectionHandler handler = config.getReconnectStrategy().create(null);
055                if (config.getFloodRate() == TS3Query.FloodRate.UNLIMITED && handler instanceof DisconnectingConnectionHandler) {
056                        // Don't wait for the last response before sending more commands
057                        receiveQueue = new LinkedBlockingQueue<>();
058                } else {
059                        // Wait for the response to the last command to arrive before sending the next one
060                        receiveQueue = new ArrayBlockingQueue<>(1);
061                }
062
063                Socket tmpSocket = null;
064                try {
065                        tmpSocket = new Socket(config.getHost(), config.getQueryPort());
066                        socket = tmpSocket;
067                        socket.setTcpNoDelay(true);
068                        socket.setSoTimeout(config.getCommandTimeout());
069
070                        socketWriter = new SocketWriter(this, config);
071                        socketReader = new SocketReader(this, socketWriter, query, config);
072                        keepAlive = new KeepAliveThread(socketWriter, query.getAsyncApi());
073                } catch (IOException e) {
074                        // Clean up resources and fail
075                        if (tmpSocket != null) {
076                                try {
077                                        tmpSocket.close();
078                                } catch (IOException ignored) {
079                                }
080                        }
081
082                        throw new TS3ConnectionFailedException(e);
083                }
084
085                // From here on: all resources have been initialized and are non-null
086                socketReader.start();
087                socketWriter.start();
088                keepAlive.start();
089        }
090
091        public void continueFrom(QueryIO io) {
092                if (io == null) return;
093                if (io.sendQueue.isEmpty() && io.receiveQueue.isEmpty()) return;
094
095                // Resend commands which remained unanswered first
096                for (ResponseBuilder responseBuilder : io.receiveQueue) {
097                        sendQueue.add(responseBuilder.getCommand());
098                }
099                sendQueue.addAll(io.sendQueue);
100
101                io.receiveQueue.clear();
102                io.sendQueue.clear();
103        }
104
105        public void disconnect() {
106                keepAlive.interrupt();
107                socketWriter.interrupt();
108                socketReader.interrupt();
109
110                try {
111                        keepAlive.join();
112                        socketWriter.join();
113                        socketReader.join();
114                } catch (final InterruptedException e) {
115                        // Restore the interrupt for the caller
116                        Thread.currentThread().interrupt();
117                }
118
119                try {
120                        socket.close();
121                } catch (IOException ignored) {
122                }
123        }
124
125        void failRemainingCommands() {
126                for (ResponseBuilder toReceive : receiveQueue) {
127                        toReceive.getCommand().getFuture().fail(new TS3QueryShutDownException());
128                }
129                for (Command toSend : sendQueue) {
130                        toSend.getFuture().fail(new TS3QueryShutDownException());
131                }
132        }
133
134        public void enqueueCommand(Command command) {
135                if (command == null) throw new IllegalArgumentException("Command cannot be null!");
136                sendQueue.add(command);
137        }
138
139        // Internals for communication with other IO classes
140
141        Socket getSocket() {
142                return socket;
143        }
144
145        BlockingQueue<Command> getSendQueue() {
146                return sendQueue;
147        }
148
149        BlockingQueue<ResponseBuilder> getReceiveQueue() {
150                return receiveQueue;
151        }
152}