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}