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.ArrayList; 039import java.util.Collection; 040import java.util.concurrent.ArrayBlockingQueue; 041import java.util.concurrent.BlockingQueue; 042import java.util.concurrent.LinkedBlockingQueue; 043 044public class QueryIO { 045 046 private final Socket socket; 047 private final SocketReader socketReader; 048 private final SocketWriter socketWriter; 049 private final KeepAliveThread keepAlive; 050 051 private final BlockingQueue<Command> sendQueue; 052 private final BlockingQueue<ResponseBuilder> receiveQueue; 053 054 QueryIO(TS3Query query, TS3Config config) { 055 sendQueue = new LinkedBlockingQueue<>(); 056 ConnectionHandler handler = config.getReconnectStrategy().create(null); 057 if (config.getFloodRate() == TS3Query.FloodRate.UNLIMITED && handler instanceof DisconnectingConnectionHandler) { 058 // Don't wait for the last response before sending more commands 059 receiveQueue = new LinkedBlockingQueue<>(); 060 } else { 061 // Wait for the response to the last command to arrive before sending the next one 062 receiveQueue = new ArrayBlockingQueue<>(1); 063 } 064 065 Socket tmpSocket = null; 066 try { 067 tmpSocket = new Socket(config.getHost(), config.getQueryPort()); 068 socket = tmpSocket; 069 socket.setTcpNoDelay(true); 070 socket.setSoTimeout(config.getCommandTimeout()); 071 072 socketWriter = new SocketWriter(this, config); 073 socketReader = new SocketReader(this, socketWriter, query, config); 074 keepAlive = new KeepAliveThread(socketWriter, query.getAsyncApi()); 075 } catch (IOException e) { 076 // Clean up resources and fail 077 if (tmpSocket != null) { 078 try { 079 tmpSocket.close(); 080 } catch (IOException ignored) { 081 } 082 } 083 084 throw new TS3ConnectionFailedException(e); 085 } 086 087 // From here on: all resources have been initialized and are non-null 088 socketReader.start(); 089 socketWriter.start(); 090 keepAlive.start(); 091 } 092 093 public void continueFrom(QueryIO io) { 094 if (io == null) return; 095 096 // Resend commands which remained unanswered first 097 io.socketReader.drainCommandsTo(sendQueue); 098 io.socketWriter.drainCommandsTo(sendQueue); 099 100 io.receiveQueue.clear(); 101 io.sendQueue.clear(); 102 } 103 104 public void disconnect() { 105 keepAlive.interrupt(); 106 socketWriter.interrupt(); 107 socketReader.interrupt(); 108 109 try { 110 keepAlive.join(); 111 socketWriter.join(); 112 socketReader.join(); 113 } catch (final InterruptedException e) { 114 // Restore the interrupt for the caller 115 Thread.currentThread().interrupt(); 116 } 117 118 try { 119 socket.close(); 120 } catch (IOException ignored) { 121 } 122 } 123 124 void failRemainingCommands() { 125 Collection<Command> commands = new ArrayList<>(receiveQueue.size() + sendQueue.size() + 1); 126 socketReader.drainCommandsTo(commands); 127 socketWriter.drainCommandsTo(commands); 128 129 for (Command command : commands) { 130 command.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}