Server.java

Index Score
xnap.plugin.nap.net
XNap 2

View: Reasons, Metrics, Source Code

These are the metrics that contribute to the Enerjy Score for this file, ranked by impact. So the metrics listed at the top influence the score to a greater extent that the metrics listed at the bottom.

MetricDescription
/* * XNap * * A pure java file sharing client. * * See AUTHORS for copyright information. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package xnap.plugin.nap.net; import xnap.XNap; import xnap.net.*; import xnap.plugin.nap.Plugin; import xnap.plugin.nap.net.msg.MessageHandler; import xnap.plugin.nap.net.msg.SendQueue; import xnap.plugin.nap.net.msg.MessageStrings; import xnap.plugin.nap.net.msg.client.ClientMessage; import xnap.plugin.nap.net.msg.client.ListChannelsMessage; import xnap.plugin.nap.net.msg.client.NickCheckMessage; import xnap.plugin.nap.net.msg.server.*; import xnap.plugin.nap.util.Channel; import xnap.plugin.nap.util.NapPreferences; import xnap.util.*; import xnap.util.prefs.StringValidator; import xnap.io.*; import java.beans.*; import java.io.*; import java.net.*; import java.util.*; import java.text.MessageFormat; import org.apache.log4j.Logger; public class Server extends AbstractRunnable implements IChatServer, Runnable { //--- Constant(s) --- // wait 30 seconds for login ack //public static final int LOGIN_TIMEOUT = 30 * 1000; public static final int SOCKET_TIMEOUT = 1 * 1000; public static final int MAX_RETRY_ON_CONNECTION_REFUSED = 3; public static final int STATUS_NOT_CONNECTED = 0; public static final int STATUS_CONNECTING = 1; public static final int STATUS_CONNECTED = 2; public static final int STATUS_LOGIN_FAILED = 4; // recoverable error public static final int STATUS_FAILED = 5; // unrecoverable error public static final int STATUS_ERROR = 6; // FIX ME public static final int STATUS_NEW_STATS = 7; public static final String[] STATUS_MSGS = { "", Plugin.tr("connecting") + "...", Plugin.tr("connected"), Plugin.tr("login failed"), Plugin.tr("failed"), Plugin.tr("connected"), Plugin.tr("error"), }; //--- Data field(s) --- protected static Logger logger = Logger.getLogger(Server.class); protected static Preferences prefs = Preferences.getInstance(); private String remoteHost; private int remotePort; private String remoteIP = null; private String network; private int userCount = -1; private String username = null; private String password = null; private String email = null; private boolean newUser; private int fileCount = -1; private int fileSize = -1; private int ping = -1; /** * Server was fetched from index service. */ private boolean temporay = false; private boolean redirector = false; private String redirectedHost; private int redirectedPort; private Socket socket; private InputStream in; private OutputStream out; private long lastLogin = 0; private NapListener listener = null; protected Search currentSearch = null; protected Browse currentBrowse = null; /** * Remembers the last <code>ErrorMessage</code>. */ protected String lastError; /** * Queue of pending searches. */ private PriorityQueue searchQueue = new PriorityQueue(); /** * Queue of pending browses. */ private LinkedList browseQueue = new LinkedList(); /** * Queue of pending messages. */ //private SendQueue sendQueue = new SendQueue(this); /* contains last n searchesTexts and their collectors */ private SearchResultCache searchCache = new SearchResultCache(); private long lastSearch = 0; /** * Hashes nick to user objects. */ protected Hashtable users = new Hashtable(); protected int searchCount = 0; /** * Hashes channel names to <code>Channel</code> objects. */ protected Hashtable channels = new Hashtable(); protected ServerVersion version = ServerVersion.UNKNOWN; protected Range shared; // do not flood server private int maxPacketsPerTick = NapPreferences.getInstance().getMaxPacketsPerTick(); private long tickLength = NapPreferences.getInstance().getTickLength(); private long writeTickPacketCount = 0; private long writeTickStart = 0; //--- Constructor(s) --- public Server(String host, String ip, int port, String network, int fileCount, int fileSize, int userCount) { status = STATUS_NOT_CONNECTED; this.remoteHost = host; this.remoteIP = ip; this.remotePort = port; this.network = network; this.userCount = userCount; this.fileCount = fileCount; this.fileSize = fileSize; } public Server(String host, int port, String network) { this(host, null, port, network, -1, -1, -1); } public Server(String host, int port) { this(host, port, ""); } public Server() { this("", 8888); } //--- Method(s) --- public boolean isConnected() { return (status == STATUS_CONNECTED); } public boolean isLoginCustomized() { return (username != null) || (password != null) || (email != null); } /** * The server is accepting messages. */ public boolean isReady() { return (status == STATUS_CONNECTING || status == STATUS_CONNECTED); } protected String getStatusMsg(int index) { StringBuffer sb = new StringBuffer(); sb.append(STATUS_MSGS[index]); if (index == STATUS_CONNECTED) { if (getLocalPort() == 0) { sb.append(" (firewalled)"); } sb.append(" ["); sb.append(searchCount); sb.append("]"); } return sb.toString(); } public IChannel create(String name) { Channel c = new Channel(this, name, "", 0); channels.put(name, c); return c; } public boolean equals(Object obj) { if (obj instanceof Server) { Server s = (Server)obj; return (getHost().equalsIgnoreCase(s.getHost()) && getPort() == s.getPort()); } return false; } public Channel getChannel(String name) { Channel c = (Channel)channels.get(name); if (c == null) { c = new Channel(this, name, "", 0); channels.put(name, c); } return c; } public IChannel[] getChannels() { Object[] values = channels.values().toArray(); IChannel[] array = new IChannel[values.length]; System.arraycopy(values, 0, array, 0, array.length); return array; } public int getFileCount() { return fileCount; } public int getFileSize() { return fileSize; } public String getEmail() { return (email != null) ? email : prefs.getEmail(); } public void setEmail(String newValue) { if (newValue != null) { try { StringValidator.EMAIL.validate(newValue); } catch (IllegalArgumentException e) { return; } } email = newValue; } public String getHost() { return remoteHost; } public void setHost(String newValue) { remoteHost = newValue; } /** * Napigator sends an ip which is sometimes more reliable than * the hostname. */ public void setIP(String newValue) { remoteIP = newValue; } public String getIP() { //return remoteIP != null ? remoteIP : socket.getInetAddress().toString(); return remoteIP; } public long getLastLogin() { return lastLogin; } /** * Returns the associated listener for incoming requests. Needed * for reverse downloads. */ public NapListener getListener() { return listener; } public void setListener(NapListener newValue) { listener = newValue; } /** * Returns port of listener or 0 if there is no listener running, which * means XNap is behind a firewall. */ public int getLocalPort() { return listener != null ? listener.getPort() : 0; } public String getName() { String network = getNetwork(); if (network.length() > 0) { return network; } else { return getHost(); } } public String getNetwork() { return network; } public void setNetwork(String newValue) { network = newValue; } public String getPassword() { return (password != null) ? password : prefs.getPassword(); } public void setPassword(String newValue) { if (newValue != null) { try { StringValidator.REGULAR_STRING.validate(newValue); } catch (IllegalArgumentException e) { return; } } password = newValue; } public int getPort() { return remotePort; } public void setPort(int newValue) { remotePort = newValue; } public String getRedirectedHost() { return redirectedHost; } public int getRedirectedPort() { return redirectedPort; } // public SendQueue getSendQueue() // { // return sendQueue; // } /** * Returns the index range of shared files. */ public Range getShared() { return (Range)shared.clone(); } public void setShared(Range newValue) { shared = (Range)newValue.clone(); } public boolean isTemporary() { return temporay; } public boolean isRedirector() { return redirector; } public void setTemporary(boolean newValue) { temporay = newValue; } public User getUser(String nick) { User u = (User)users.get(nick); if (u == null) { u = new User(nick, this); users.put(nick, u); } return u; } public IUser getUser() { return getUser(getUsername()); } public int getUserCount() { return userCount; } public String getUsername() { return (username != null) ? username : prefs.getUsername(); } public void setUsername(String newValue) { if (newValue != null) { try { StringValidator.REGULAR_STRING.validate(newValue); } catch (IllegalArgumentException e) { return; } } username = newValue; } public ServerVersion getVersion() { return version; } public void setVersion(String newValue) { version = new ServerVersion(newValue); } public void setRedirectedHost(String redirectedHost) { this.redirectedHost = redirectedHost; } public void setRedirectedPort(int redirectedPort) { this.redirectedPort = redirectedPort; } public void setRedirector(boolean newValue) { this.redirector = newValue; } /** * Logs into server. */ public void login(boolean newUser) { if (!canStart()) { return; } lastLogin = System.currentTimeMillis(); setStatus(STATUS_CONNECTING); this.newUser = newUser; // start main loop to listen for msgs runner = new Thread(this, "OpenNapServer " + getHost()); runner.start(); } /** * Connects to a redirector server and reads the host information. * * @return true, if successful; false, if failed */ public boolean fetchHost(String host, int port) throws IOException { Socket socket = null; InputStream in = null; try { socket = new Socket(host, port); in = new BufferedInputStream(socket.getInputStream()); byte[] b = new byte[1024]; int c = in.read(b, 0, 1024); if (c > 0) { // chop the \n String response = new String(b, 0, c - 1); StringTokenizer t = new StringTokenizer(response, ":"); if (t.countTokens() == 2) { try { setRedirectedHost(t.nextToken()); setRedirectedPort (Integer.parseInt(t.nextToken())); return true; } catch (NumberFormatException e) { } } } } finally { try { if (in != null) { in.close(); } if (socket != null) { socket.close(); } } catch (IOException e) { } } return false; } public void logout() { die(STATUS_NOT_CONNECTED); } public void messageReceived(String channelName, String nick, String message) { Channel c = (Channel)channels.get(channelName); if (c != null) { c.messageReceived(getUser(nick), message); } else { StringBuffer sb = new StringBuffer(); sb.append(getHost()); sb.append(" ("); sb.append(channelName); sb.append(") : "); sb.append(message); ChatManager.getInstance().globalMessageReceived(sb.toString()); } } public synchronized void addBrowse(Browse b) throws IOException { if (!isConnected()) { throw new IOException("server disconnected"); } browseQueue.add(b); allowBrowse(); } public synchronized void addSearch(Search s) throws IOException { if (!isConnected()) { throw new IOException("server disconnected"); } int i; if (currentSearch != null && currentSearch.equals(s)) { for (Iterator it = currentSearch.getResults().iterator(); it.hasNext();) { s.add((SearchResult)it.next()); } currentSearch.addPeer(s); } else if ((i = searchQueue.indexOf(s)) != -1) { ((Search)searchQueue.get(i)).addPeer(s); } else if (!useSearchCache(s)) { searchQueue.add(s, s.getPriority()); allowSearch(); } } /** * Sleeps until ready to send. */ public void waitUntilReadyToSend() { long tickLeft = tickLength - (System.currentTimeMillis() - writeTickStart); if (tickLeft <= 0) { writeTickStart = System.currentTimeMillis(); writeTickPacketCount = 0; } else if (writeTickPacketCount >= maxPacketsPerTick) { //logger.debug("flood protection: stalling for " // + tickLeft + " ms"); try { Thread.currentThread().sleep(tickLeft); } catch (InterruptedException e) { } } } public synchronized void removeBrowse(Browse b) { browseQueue.remove(b); } /** * Is only called when search didn't take place or was aborted. That's * why the cache entry is removed too. */ public synchronized void removeSearch(Search s) { searchQueue.remove(s); } public void send(ClientMessage msg) throws IOException { sendPacket(new Packet(msg.getType(), msg.getData(version))); } public void updateChannels() throws IOException { MessageHandler.send(this, new ListChannelsMessage()); } private ServerMessage getNextMessage() { Packet p = null; while (!die) { try { p = recvPacket(); return MessageFactory.create(this, p.id, p.data); } catch (InterruptedIOException e) { } catch (IOException e) { logger.error(getHost() + ":" + getPort() + " getNextMessage: " + e.getMessage()); logger.debug(getHost() + ":" + getPort() + " getNextMessage ", e); if (getStatus() == STATUS_CONNECTING) { die(STATUS_LOGIN_FAILED, lastError); } else { die(STATUS_FAILED, lastError); } } catch (InvalidMessageException e) { logger.error(getHost() + ":" + getPort() + " getNextMessage: " + p, e); } } return null; } private byte[] read(int length) throws IOException { byte[] textBuf = new byte[length]; int read = 0; while (read < length) { try { int c = in.read(textBuf, read, length - read); if (c == -1) { throw new IOException("Connection closed"); } read += c; } catch (InterruptedIOException e) { if (die) { throw e; } } } return textBuf; } private Packet recvPacket() throws IOException { String content = ""; byte[] header = read(4); // byte types are signed... int lo = 0xFF & (int)header[0]; int hi = 0xFF & (int)header[1]; int length = 0xFFFF & (hi << 8 | lo); lo = 0xFF & (int)header[2]; hi = 0xFF & (int)header[3]; int id = 0xFFFF & (hi << 8 | lo); if (length > 0) { content = new String(read(length)); } logger.debug("< " + getHost() + ":" + getPort() + " " + MessageFactory.getMessageName(id) + "(" +id + ") " + content); return new Packet(id, content); } private void sendPacket(Packet p) throws IOException { logger.debug("> " + getHost() + ":" + getPort() + " " + MessageStrings.getMessageName(p.id) + "(" + p.id + ") " + p.data); // logger.debug("> " + getHost() + ":" + getPort() + " (" + p.id + ") " // + p.data); try { int dataLength = p.data.getBytes().length; byte[] data = new byte[2 + 2 + dataLength]; int type = (short)p.id; int len = (short)dataLength; data[0] = (byte)len; data[1] = (byte)(len >> 8); data[2] = (byte)type; data[3] = (byte)(type >> 8); System.arraycopy(p.data.getBytes(), 0, data, 4, dataLength); // maybe we should do some synchronization here // we don't want to have multiple threads write half packets out.write(data); out.flush(); // flood protection writeTickPacketCount++; } catch (NullPointerException e) { logger.debug(getHost() + ":" + getPort() + " sendPacket " + p, e); if (getStatus() == STATUS_CONNECTING) { die(STATUS_LOGIN_FAILED, e.getMessage()); } else { die(STATUS_FAILED, e.getMessage()); } throw(new IOException("Internal error")); } catch (IOException e) { logger.debug(getHost() + ":" + getPort() + " sendPacket " + p, e); if (getStatus() == STATUS_CONNECTING) { die(STATUS_LOGIN_FAILED, e.getMessage()); } else { die(STATUS_FAILED, e.getMessage()); } throw(e); } } /** * Opens the socket to the server and connects the streams. * If host1 != null, it is tried first. * * @param host1 the ip address of the server or null if unknown * @param host2 the hostname of the server * @param port the port of the server */ private void connect(String host1, String host2, int port) throws IOException { socket = null; if (host1 != null) { try { socket = new Socket(host1, port); } catch (IOException e) { setIP(null); } } if (socket == null) { for (int i = 0; i < MAX_RETRY_ON_CONNECTION_REFUSED && socket == null; i++) { try { socket = new Socket(host2, port); } catch (ConnectException e) { if (i == MAX_RETRY_ON_CONNECTION_REFUSED - 1) { throw e; } try { Thread.sleep(10); } catch (InterruptedException e2) { } } } } socket.setSoTimeout(SOCKET_TIMEOUT); in = new BufferedInputStream(socket.getInputStream()); out = socket.getOutputStream(); setStatus(STATUS_CONNECTING, XNap.tr("logging in") + "..."); // server.setStateDescription(XNap.tr("logging in") + "..."); try { if (newUser) { send(new NickCheckMessage(getUsername())); } else { // login(false); MessageHandler.login(this, false); } } catch (IOException e) { die(STATUS_LOGIN_FAILED, XNap.tr("login failed")); } } /** * Waits for msgs from napster server and passes them on to the * individual MsgQueues. */ public void run() { Thread.currentThread().setPriority(Thread.MIN_PRIORITY); die = false; currentBrowse = null; currentSearch = null; searchCount = 0; lastError = null; searchCache.clear(); users.clear(); try { if (isRedirector()) { if (fetchHost(getHost(), getPort())) { connect(null, getRedirectedHost(), getRedirectedPort()); } else { die(STATUS_FAILED, "invalid response"); } } else { connect(remoteIP, getHost(), getPort()); } } catch (ConnectException e) { // this is connection refused, try again later // die(STATUS_FAILED, MessageFormat.format // (XNap.tr("failed: {0}"), // new Object[] { e.getLocalizedMessage() })); die(STATUS_FAILED, XNap.tr("connection refused")); } catch (UnknownHostException e) { die(STATUS_ERROR, XNap.tr("unknown host")); } catch (IOException e) { // not route to host or something, probably unrecoverable die(STATUS_ERROR, XNap.tr("error: {0}", e.getLocalizedMessage())); } while (!die) { ServerMessage msg = getNextMessage(); if (die) { break; } if (msg instanceof NickNotRegisteredMessage) { if (getStatus() == STATUS_CONNECTING) { try { MessageHandler.login(this, true); } catch (IOException e) { setStatus(STATUS_LOGIN_FAILED, e.getMessage()); } } } else if (msg instanceof NickAlreadyRegisteredMessage) { die(STATUS_LOGIN_FAILED, Plugin.tr("nick already registered")); } else if (msg instanceof InvalidNickMessage) { die(STATUS_LOGIN_FAILED, Plugin.tr("invalid nick")); } else if (msg instanceof LoginAckMessage) { if (getStatus() == STATUS_CONNECTING) { //sendQueue.clear(); setStatus(STATUS_CONNECTED); } } else if (msg instanceof LoginErrorMessage) { die(STATUS_LOGIN_FAILED, ((LoginErrorMessage)msg).message); } else if (msg instanceof BrowseResponseMessage) { if (currentBrowse != null) { currentBrowse.add((BrowseResponseMessage)msg); } } else if (msg instanceof EndBrowseMessage) { if (currentBrowse != null) { currentBrowse.finished(); currentBrowse = null; updateStatus(); } } else if (msg instanceof SearchResponseMessage) { if (currentSearch != null) { currentSearch.add((SearchResponseMessage)msg); } } else if (msg instanceof EndSearchMessage) { if (currentSearch != null) { synchronized (this) { currentSearch.finished(); // create cache entry if auto download search if (currentSearch.getPriority() == ISearch.PRIORITY_BACKGROUND) { searchCache.put(currentSearch.getRequest(), currentSearch.getResults()); } currentSearch = null; } updateStatus(); } } else if (msg instanceof ServerStatsMessage) { ServerStatsMessage ssm = (ServerStatsMessage)msg; userCount = ssm.userCount; fileCount = ssm.fileCount; fileSize = ssm.fileSize; fireStatusChange(status, STATUS_NEW_STATS); } else if (msg instanceof ErrorMessage) { lastError = ((ErrorMessage)msg).message; if (lastError.startsWith("You have been killed by server") || lastError.startsWith("You were killed by server")) { setStatus(STATUS_ERROR, lastError); } } MessageHandler.handle(msg); allowSearch(); allowBrowse(); } try { if (in != null) in.close(); if (out != null) out.close(); if (socket != null) socket.close(); } catch (IOException e) { } synchronized (this) { if (currentBrowse != null) { currentBrowse.failed(Plugin.tr("Server disconnected")); } for (Iterator i = browseQueue.iterator(); i.hasNext();) { ((Browse)i.next()).failed(Plugin.tr("Server disconnected")); } browseQueue.clear(); } synchronized (this) { if (currentSearch != null) { currentSearch.failed(Plugin.tr("Server disconnected")); } Object[] searches = searchQueue.toArray(); for (int i = 0; i < searches.length; i++) { ((Search)searches[i]).failed(Plugin.tr("Server disconnected")); } searchQueue.clear(); } // close all open chat channels for (Iterator i = channels.values().iterator(); i.hasNext();) { ((IChannel)i.next()).close(); } logger.debug(toString() + " has died"); died(); } private void updateStatus() { if (status == STATUS_CONNECTED) { StringBuffer sb = new StringBuffer(getStatusMsg(status)); if (currentSearch != null) { sb.append(", searching"); } if (searchQueue.size() > 0) { sb.append(", "); sb.append(searchQueue.size()); sb.append(" searches pending"); } if (currentBrowse != null) { sb.append(", browsing"); } setStatus(status, sb.toString()); } } public String toString() { StringBuffer sb = new StringBuffer(); sb.append(getHost()); sb.append(":"); sb.append(getPort()); String network = getNetwork(); if (network != null && !network.equals("")) { sb.append(" ("); sb.append(network); sb.append(")"); } return sb.toString(); } private synchronized void allowBrowse() { if (currentBrowse != null || browseQueue.size() == 0) { return; } currentBrowse = (Browse)browseQueue.removeFirst(); currentBrowse.start(); updateStatus(); } private synchronized void allowSearch() { if (currentSearch != null || searchQueue.size() == 0) { return; } Search s = (Search)searchQueue.peek(); if (useSearchCache(s)) { searchQueue.pop(); return; } searchQueue.pop(); currentSearch = s; lastSearch = System.currentTimeMillis(); searchCount++; currentSearch.start(); updateStatus(); } private synchronized boolean useSearchCache(Search s) { if (s.getPriority() < ISearch.PRIORITY_USER) { searchCache.purge(); LinkedList results = searchCache.get(s.getRequest()); if (results != null) { // cache hit logger.debug("nap server: reuse of searchresults"); for (Iterator i = results.iterator(); i.hasNext();) { SearchResult sr = (SearchResult)i.next(); s.add(sr); } s.close(); return true; } } return false; } }

The table below shows all metrics for Server.java.

MetricValueDescription