/* * This file is part of Spoutcraft. * * Copyright (c) 2011 SpoutcraftDev <http://spoutcraft.org/> * Spoutcraft is licensed under the GNU Lesser General Public License. * * Spoutcraft is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Spoutcraft is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.spoutcraft.client.gui.server; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.Socket; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import gnu.trove.map.hash.TIntObjectHashMap; import org.spoutcraft.client.SpoutClient; import org.spoutcraft.client.util.NetworkUtils; public class PollResult { protected int ping; protected int players; protected int maxPlayers; protected String motd = ""; protected int protocolVersion; protected String version; protected String ip; protected int port; protected int databaseId = -1; public static final int PING_POLLING = -1; public static final int PING_UNKNOWN = -2; public static final int PING_TIMEOUT = -3; public static final int PING_BAD_MESSAGE = -4; protected boolean polling = false; protected long pollStart; protected static volatile int numPolling = 0; private static final int maxPollingThreads; protected PollThread currentThread; protected ServerModel favorites = SpoutClient.getInstance().getServerManager().getFavorites(); protected ServerListModel serverList = SpoutClient.getInstance().getServerManager().getServerList(); protected static TIntObjectHashMap<PollResult> recentResults = new TIntObjectHashMap<PollResult>(); protected static Thread send = null; private boolean sent = false; static { maxPollingThreads = 5 + (5 * Runtime.getRuntime().availableProcessors()); } protected PollResult(String ip, int port, int uid) { setIp(ip); setPort(port); setDatabaseId(uid); } public int getPing() { return ping; } public void setPing(int ping) { this.ping = ping; } public int getPlayers() { return players; } public void setPlayers(int players) { this.players = players; } public int getMaxPlayers() { return maxPlayers; } public void setMaxPlayers(int maxPlayers) { this.maxPlayers = maxPlayers; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public void poll() { if (!isPolling()) { currentThread = new PollThread(); currentThread.start(); } } public static PollResult getPoll(String ip, int port, int uid) { String hash = ip + ":" + port; PollResult result = recentResults.get(hash.hashCode()); if (result == null) { result = new PollResult(ip, port, uid); recentResults.put(hash.hashCode(), result); result.poll(); } return result; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public int getProtocolVersion() { return protocolVersion; } public void setProtocolVersion(int protocolVersion) { this.protocolVersion = protocolVersion; } public String getMotd() { return motd; } public void setMotd(String motd) { this.motd = motd; } public int getDatabaseId() { return databaseId; } public void setDatabaseId(int databaseId) { this.databaseId = databaseId; } public boolean isPolling() { return polling; } public void endPolling() { if (polling) { polling = false; numPolling--; } } protected class PollThread extends Thread { @Override public void run() { while (numPolling >= maxPollingThreads) { try { sleep(10); } catch (InterruptedException e) {} } numPolling ++; polling = true; favorites.setPolling(true); Socket sock = null; DataInputStream input = null; DataOutputStream output = null; try { long start = System.currentTimeMillis(); sock = new Socket(); sock.setSoTimeout(10000); InetSocketAddress address = NetworkUtils.resolve(ip, port); if (address.isUnresolved()) { ping = PING_UNKNOWN; return; } sock.connect(address, 10000); sock.setTcpNoDelay(true); sock.setTrafficClass(18); input = new DataInputStream(sock.getInputStream()); output = new DataOutputStream(sock.getOutputStream()); // Packet ID is 254 output.write(254); // Writes 1 for getting extended server information since 1.4 output.writeByte(1); // Server will return a packet 255 with the data as string if (input.read() != 255) { ping = PING_BAD_MESSAGE; return; } StringBuilder builder = new StringBuilder(); short size = input.readShort(); for (int i = 0; i < size; i++) { builder.append(input.readChar()); } long end = System.currentTimeMillis(); String sPacket = builder.toString(); ping = (int) (end - start); // Check if we have new format here (1.4), and fall back to old format if not if (sPacket.startsWith("\u00a71")) { String split[] = sPacket.split("\u0000"); protocolVersion = Integer.valueOf(split[1]); version = split[2]; synchronized (motd) { motd = split[3]; } players = Integer.valueOf(split[4]); maxPlayers = Integer.valueOf(split[5]); } else { String split[] = sPacket.split("\u00a7"); synchronized (motd) { motd = split[0]; } players = Integer.valueOf(split[1]); maxPlayers = Integer.valueOf(split[2]); } } catch(java.net.UnknownHostException e) { ping = PING_UNKNOWN; } catch(IOException e) { ping = PING_TIMEOUT; } catch (Exception e) { ping = PING_BAD_MESSAGE; } finally { polling = false; numPolling--; if (numPolling == 0) { favorites.setPolling(false); } if (SpoutClient.getHandle().currentScreen instanceof GuiServerInfo) { GuiServerInfo screen = (GuiServerInfo) SpoutClient.getHandle().currentScreen; screen.updateData(); } try { sock.close(); input.close(); output.close(); } catch(Exception e) {} } } } }