001package com.github.theholywaffle.teamspeak3; 002 003/* 004 * #%L 005 * TeamSpeak 3 Java API 006 * %% 007 * Copyright (C) 2014 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.Callback; 030import com.github.theholywaffle.teamspeak3.commands.CQuit; 031import com.github.theholywaffle.teamspeak3.commands.Command; 032 033import java.io.BufferedReader; 034import java.io.IOException; 035import java.io.InputStreamReader; 036import java.util.Iterator; 037import java.util.Map; 038import java.util.Set; 039import java.util.logging.Level; 040 041public class SocketReader extends Thread { 042 043 private final TS3Query ts3; 044 private final QueryIO io; 045 private final BufferedReader in; 046 047 private String lastEvent = ""; 048 049 public SocketReader(TS3Query ts3Query, QueryIO io) throws IOException { 050 super("[TeamSpeak-3-Java-API] SocketReader"); 051 this.io = io; 052 this.ts3 = ts3Query; 053 054 // Connect 055 this.in = new BufferedReader(new InputStreamReader(io.getSocket().getInputStream(), "UTF-8")); 056 try { 057 int i = 0; 058 while (i < 4 || in.ready()) { 059 TS3Query.log.info("< " + in.readLine()); 060 i++; 061 } 062 } catch (final IOException e) { 063 e.printStackTrace(); 064 } 065 } 066 067 @Override 068 public void run() { 069 while (!isInterrupted()) { 070 final String line; 071 072 try { 073 // Will block until a full line of text could be read. 074 line = in.readLine(); 075 } catch (IOException io) { 076 if (!isInterrupted()) { 077 TS3Query.log.log(Level.WARNING, "Connection error occurred.", io); 078 } 079 break; 080 } 081 082 if (line == null) { 083 // End of stream: connection terminated by server 084 TS3Query.log.warning("Connection closed by the server."); 085 break; 086 } else if (line.isEmpty()) { 087 continue; // The server is sending garbage 088 } 089 090 final Command c = io.getCommandQueue().peek(); 091 092 if (line.startsWith("notify")) { 093 TS3Query.log.info("< [event] " + line); 094 095 // Filter out duplicate events for join, quit and channel move events 096 if (isDuplicate(line)) continue; 097 098 ts3.submitUserTask(new Runnable() { 099 @Override 100 public void run() { 101 final String arr[] = line.split(" ", 2); 102 ts3.getEventManager().fireEvent(arr[0], arr[1]); 103 } 104 }); 105 } else if (c != null && c.isSent()) { 106 TS3Query.log.info("[" + c.getName() + "] < " + line); 107 if (line.startsWith("error")) { 108 if (c instanceof CQuit) { 109 // Response to a quit command received, we're done 110 interrupt(); 111 } 112 113 c.feedError(line.substring("error ".length())); 114 if (c.getError().getId() != 0) { 115 TS3Query.log.severe("TS3 command error: " + c.getError()); 116 } 117 c.setAnswered(); 118 io.getCommandQueue().remove(c); 119 answerCallback(c); 120 } else { 121 c.feed(line); 122 } 123 } else { 124 TS3Query.log.warning("[UNHANDLED] < " + line); 125 } 126 } 127 128 try { 129 in.close(); 130 } catch (IOException ignored) { 131 // Ignore 132 } 133 134 if (!isInterrupted()) { 135 TS3Query.log.warning("SocketReader has stopped!"); 136 ts3.fireDisconnect(); 137 } 138 } 139 140 private void answerCallback(Command c) { 141 final Map<Command, Callback> callbackMap = io.getCallbackMap(); 142 final Callback callback = callbackMap.get(c); 143 144 // Command had no callback registered 145 if (callback == null) return; 146 147 // To avoid the possibility of clogging the map with callbacks for 148 // inexistent commands, remove all entries before the current one 149 // Typically, this will exit without removing a single entry 150 Set<Command> keySet = callbackMap.keySet(); 151 synchronized (callbackMap) { 152 Iterator<Command> iterator = keySet.iterator(); 153 while (iterator.hasNext() && !c.equals(iterator.next())) { 154 iterator.remove(); 155 } 156 } 157 158 ts3.submitUserTask(new Runnable() { 159 @Override 160 public void run() { 161 try { 162 callback.handle(); 163 } catch (Throwable t) { 164 TS3Query.log.log(Level.WARNING, "User callback threw exception", t); 165 } 166 } 167 }); 168 } 169 170 private boolean isDuplicate(String eventMessage) { 171 if (!(eventMessage.startsWith("notifyclientmoved") 172 || eventMessage.startsWith("notifycliententerview") 173 || eventMessage.startsWith("notifyclientleftview"))) { 174 175 // Event that will never cause duplicates 176 return false; 177 } 178 179 if (eventMessage.equals(lastEvent)) { 180 // Duplicate event! 181 lastEvent = ""; // Let's only ever filter one duplicate 182 return true; 183 } 184 185 lastEvent = eventMessage; 186 return false; 187 } 188}