X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Firc%2FConnection.java;h=580b4d786401d3fd19243d988f157765c232b9ba;hb=f8b1b38d714ac20b94867919582cce31ef7b807c;hp=0e7c4612f007e6e8e23806803f63c64e70ea600f;hpb=c74521454ed57b89ae52624194a48b8173369603;p=xudocci.git diff --git a/src/main/java/net/pterodactylus/irc/Connection.java b/src/main/java/net/pterodactylus/irc/Connection.java index 0e7c461..580b4d7 100644 --- a/src/main/java/net/pterodactylus/irc/Connection.java +++ b/src/main/java/net/pterodactylus/irc/Connection.java @@ -1,159 +1,43 @@ -/* - * XdccDownloader - Connection.java - Copyright © 2013 David Roden - * - * 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 3 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, see . - */ - package net.pterodactylus.irc; -import static com.google.common.base.Preconditions.checkState; - -import java.io.BufferedReader; -import java.io.Closeable; -import java.io.EOFException; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.Socket; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.net.SocketFactory; - -import net.pterodactylus.irc.event.ChannelJoined; -import net.pterodactylus.irc.event.ChannelMessageReceived; -import net.pterodactylus.irc.event.ChannelNicknames; -import net.pterodactylus.irc.event.ChannelNotJoined; -import net.pterodactylus.irc.event.ChannelNotJoined.Reason; -import net.pterodactylus.irc.event.ChannelTopic; -import net.pterodactylus.irc.event.ConnectionEstablished; -import net.pterodactylus.irc.event.ConnectionFailed; -import net.pterodactylus.irc.event.MotdReceived; -import net.pterodactylus.irc.event.NicknameInUseReceived; -import net.pterodactylus.irc.event.NoNicknameGivenReceived; -import net.pterodactylus.irc.event.PrivateMessageReceived; -import net.pterodactylus.irc.event.UnknownReplyReceived; -import net.pterodactylus.irc.util.RandomNickname; - -import com.beust.jcommander.internal.Maps; -import com.beust.jcommander.internal.Sets; -import com.google.common.base.Optional; -import com.google.common.eventbus.EventBus; -import com.google.common.io.Closeables; -import com.google.common.util.concurrent.AbstractExecutionThreadService; -import com.google.common.util.concurrent.Service; /** * A connection to an IRC server. * * @author David ‘Bombe’ Roden */ -public class Connection extends AbstractExecutionThreadService implements Service { - - /* The logger. */ - private static final Logger logger = Logger.getLogger(Connection.class.getName()); - - /** The event bus. */ - private final EventBus eventBus; - - /** The socket factory. */ - private final SocketFactory socketFactory; - - /** The hostname to connect to. */ - private final String hostname; - - /** The port to connect to. */ - private final int port; - - /** The nickname chooser. */ - private NicknameChooser nicknameChooser = new NicknameChooser() { - - @Override - public String getNickname() { - return RandomNickname.get(); - } - }; - - /** The nickname. */ - private String nickname = null; - - /** The username. */ - private Optional username = Optional.absent(); - - /** The real name. */ - private Optional realName = Optional.absent(); - - /** The optional password for the connection. */ - private Optional password = Optional.absent(); - - /** The connection handler. */ - private ConnectionHandler connectionHandler; +public interface Connection { /** - * Creates a new connection. + * Returns the hostname of the remote end of the connection. * - * @param eventBus - * The event bus - * @param socketFactory - * The socket factory - * @param hostname - * The hostname of the IRC server - * @param port - * The port number of the IRC server + * @return The remote’s hostname */ - public Connection(EventBus eventBus, SocketFactory socketFactory, String hostname, int port) { - this.eventBus = eventBus; - this.socketFactory = socketFactory; - this.hostname = hostname; - this.port = port; - } - - // - // ACCESSORS - // + String hostname(); /** - * Returns the nickname that is currently in use by this connection. The - * nickname is only available once the connection has been {@link #start()}ed. + * Returns the port number of the remote end of the connection. * - * @return The current nickname + * @return The remote’s port number */ - public String nickname() { - return nickname; - } + int port(); - // - // MUTATORS - // + /** + * Returns whether this connection has already been established. + * + * @return {@code true} as long as this connection is established, {@code + * false} otherwise + */ + boolean established(); /** - * Sets the nickname chooser. The nickname chooser is only used during the - * creation of the connection. + * Returns the nickname that is currently in use by this connection. The + * nickname is only available once the connection has been {@link #start()}ed. * - * @param nicknameChooser - * The nickname chooser - * @return This connection + * @return The current nickname */ - public Connection nicknameChooser(NicknameChooser nicknameChooser) { - this.nicknameChooser = nicknameChooser; - return this; - } + String nickname(); /** * Sets the username to use. @@ -162,10 +46,7 @@ public class Connection extends AbstractExecutionThreadService implements Servic * The username to use * @return This connection */ - public Connection username(String username) { - this.username = Optional.fromNullable(username); - return this; - } + Connection username(String username); /** * Sets the real name to use. @@ -174,10 +55,7 @@ public class Connection extends AbstractExecutionThreadService implements Servic * The real name to use * @return This connection */ - public Connection realName(String realName) { - this.realName = Optional.fromNullable(realName); - return this; - } + Connection realName(String realName); /** * Sets the optional password for the connection. @@ -186,14 +64,32 @@ public class Connection extends AbstractExecutionThreadService implements Servic * The password for the connection * @return This connection */ - public Connection password(String password) { - this.password = Optional.fromNullable(password); - return this; - } + Connection password(String password); + + /** + * Returns the current rate of the connection’s incoming side. + * + * @return The current input rate (in bytes per second) + */ + long getInputRate(); - // - // ACTIONS - // + /** + * Returns the current rate of the connection’s outgoing side. + * + * @return The current output rate (in bytes per second) + */ + long getOutputRate(); + + /** + * Checks whether the given source is the client represented by this + * connection. + * + * @param source + * The source to check + * @return {@code true} if this connection represents the given source, {@code + * false} otherwise + */ + boolean isSource(Source source); /** * Joins the given channel. @@ -203,315 +99,49 @@ public class Connection extends AbstractExecutionThreadService implements Servic * @throws IOException * if an I/O error occurs */ - public void joinChannel(final String channel) throws IOException { - connectionHandler.sendCommand("JOIN", channel); - } - - // - // ABSTRACTEXECUTIONTHREADSERVICE METHODS - // - - @Override - protected void startUp() throws IllegalStateException { - checkState(username.isPresent(), "username must be set"); - checkState(realName.isPresent(), "realName must be set"); - } - - @Override - protected void run() { - - /* connect to remote socket. */ - try { - Socket socket = socketFactory.createSocket(hostname, port); - connectionHandler = new ConnectionHandler(socket.getInputStream(), socket.getOutputStream()); - - /* register connection. */ - if (password.isPresent()) { - connectionHandler.sendCommand("PASSWORD", password.get()); - } - connectionHandler.sendCommand("USER", username.get(), "8", "*", realName.get()); - nickname = nicknameChooser.getNickname(); - connectionHandler.sendCommand("NICK", nickname); - - } catch (IOException ioe1) { - eventBus.post(new ConnectionFailed(this, ioe1)); - return; - } - - /* now read replies and react. */ - try { - /* some status variables. */ - int oldConnectionStatus = 0; - int connectionStatus = 0; - boolean connected = true; - StringBuilder motd = new StringBuilder(); - Set nicks = Sets.newHashSet(); - - /* server modes. */ - Map nickPrefixes = Maps.newHashMap(); - Set channelTypes = Sets.newHashSet(); - - while (connected) { - Reply reply = connectionHandler.readReply(); - logger.finest(String.format("<< %s", reply)); - String command = reply.command(); - List parameters = reply.parameters(); - - /* most common events. */ - if (command.equalsIgnoreCase("PRIVMSG")) { - String recipient = parameters.get(0); - if (!channelTypes.contains(recipient.charAt(0))) { - eventBus.post(new PrivateMessageReceived(this, reply.source().get(), parameters.get(1))); - } else { - eventBus.post(new ChannelMessageReceived(this, recipient, reply.source().get(), parameters.get(1))); - } - - /* replies 001-004 don’t hold information but they have to be sent on a successful connection. */ - } else if (command.equals("001")) { - connectionStatus |= 0x01; - } else if (command.equals("002")) { - connectionStatus |= 0x02; - } else if (command.equals("003")) { - connectionStatus |= 0x04; - } else if (command.equals("004")) { - connectionStatus |= 0x08; - - /* 005 originally was a bounce message, now used to transmit useful information about the server. */ - } else if (command.equals("005")) { - for (String parameter : parameters) { - if (parameter.startsWith("PREFIX=")) { - int openParen = parameter.indexOf('('); - int closeParen = parameter.indexOf(')'); - if ((openParen != -1) && (closeParen != -1)) { - for (int modeCharacterIndex = 1; modeCharacterIndex < (closeParen - openParen); ++modeCharacterIndex) { - char modeCharacter = parameter.charAt(openParen + modeCharacterIndex); - char modeSymbol = parameter.charAt(closeParen + modeCharacterIndex); - nickPrefixes.put(String.valueOf(modeSymbol), String.valueOf(modeCharacter)); - } - logger.fine(String.format("Parsed Prefixes: %s", nickPrefixes)); - } - } else if (parameter.startsWith("CHANTYPES=")) { - for (int typeIndex = 10; typeIndex < parameter.length(); ++typeIndex) { - channelTypes.add(parameter.charAt(typeIndex)); - } - logger.fine(String.format("Parsed Channel Types: %s", channelTypes)); - } - } - - /* 375, 372, and 376 handle the server’s MOTD. */ - } else if (command.equals("375")) { - /* MOTD starts. */ - motd.append(parameters.get(1)).append('\n'); - } else if (command.equals("372")) { - motd.append(parameters.get(1)).append('\n'); - } else if (command.equals("376")) { - motd.append(parameters.get(1)).append('\n'); - eventBus.post(new MotdReceived(this, motd.toString())); - motd.setLength(0); - - /* 43x replies are for nick change errors. */ - } else if (command.equals("431")) { - eventBus.post(new NoNicknameGivenReceived(this, reply)); - } else if (command.equals("433")) { - if (connectionStatus == 0) { - nickname = nicknameChooser.getNickname(); - connectionHandler.sendCommand("NICK", nickname); - } else { - eventBus.post(new NicknameInUseReceived(this, reply)); - } - - /* channel stuff. */ - } else if (command.equalsIgnoreCase("JOIN")) { - eventBus.post(new ChannelJoined(this, parameters.get(0), reply.source().get())); - } else if (command.equals("331")) { - /* no topic is set. */ - } else if (command.equals("332")) { - eventBus.post(new ChannelTopic(this, parameters.get(1), parameters.get(2))); - } else if (command.equals("353")) { - for (String nickname : parameters.get(3).split(" ")) { - if (nickPrefixes.containsKey(nickname.substring(0, 1))) { - nicks.add(new Nickname(nickname.substring(1), nickname.substring(0, 1))); - } else { - nicks.add(new Nickname(nickname, "")); - } - } - } else if (command.equals("366")) { - eventBus.post(new ChannelNicknames(this, parameters.get(1), nicks)); - nicks.clear(); - - /* common channel join errors. */ - } else if (command.equals("474")) { - eventBus.post(new ChannelNotJoined(this, parameters.get(1), Reason.banned)); - } else if (command.equals("473")) { - eventBus.post(new ChannelNotJoined(this, parameters.get(1), Reason.inviteOnly)); - } else if (command.equals("475")) { - eventBus.post(new ChannelNotJoined(this, parameters.get(1), Reason.badChannelKey)); - - /* basic connection housekeeping. */ - } else if (command.equalsIgnoreCase("PING")) { - connectionHandler.sendCommand("PONG", getOptional(parameters, 0), getOptional(parameters, 1)); - - /* okay, everything else. */ - } else { - eventBus.post(new UnknownReplyReceived(this, reply)); - } - - if ((connectionStatus == 0x0f) && (connectionStatus != oldConnectionStatus)) { - /* connection succeeded! */ - eventBus.post(new ConnectionEstablished(this)); - } - oldConnectionStatus = connectionStatus; - } - } catch (IOException ioe1) { - logger.log(Level.WARNING, "I/O error", ioe1); - } finally { - logger.info("Closing Connection."); - try { - Closeables.close(connectionHandler, true); - } catch (IOException ioe1) { - /* will not be thrown. */ - } - } - - } - - // - // PRIVATE METHODS - // + void joinChannel(String channel) throws IOException; /** - * Returns an item from the list, or {@link Optional#absent()} if the list is - * shorter than required for the given index. + * Sends a message to the given recipient, which may be a channel or another + * nickname. * - * @param list - * The list to get an item from - * @param index - * The index of the item - * @param - * The type of the list items - * @return This list item wrapped in an {@link Optional}, or {@link - * Optional#absent()} if the list is not long enough + * @param recipient + * The recipient of the message + * @param message + * The message + * @throws IOException + * if an I/O error occurs */ - private static Optional getOptional(List list, int index) { - if (index < list.size()) { - return Optional.fromNullable(list.get(index)); - } - return Optional.absent(); - } - - /** Handles input and output for the connection. */ - private class ConnectionHandler implements Closeable { - - /** The output stream of the connection. */ - private final OutputStream outputStream; + void sendMessage(String recipient, String message) throws IOException; - /** The input stream of the connection. */ - private final BufferedReader inputStreamReader; - - /** - * Creates a new connection handler for the given input stream and output - * stream. - * - * @param inputStream - * The input stream of the connection - * @param outputStream - * The output stream of the connection - * @throws UnsupportedEncodingException - * if the encoding (currently “UTF-8”) is not valid - */ - private ConnectionHandler(InputStream inputStream, OutputStream outputStream) throws UnsupportedEncodingException { - this.outputStream = outputStream; - inputStreamReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); - } - - // - // ACTIONS - // - - /** - * Sends a command with the given parameters, skipping all {@link - * Optional#absent()} optionals. - * - * @param command - * The command to send - * @param parameters - * The parameters - * @throws IOException - * if an I/O error occurs - */ - public void sendCommand(String command, Optional... parameters) throws IOException { - List setParameters = new ArrayList(); - for (Optional maybeSetParameter : parameters) { - if (maybeSetParameter.isPresent()) { - setParameters.add(maybeSetParameter.get()); - } - } - sendCommand(command, setParameters.toArray(new String[0])); - } - - /** - * Sends a command with the given parameters. - * - * @param command - * The command to send - * @param parameters - * The parameters of the command - * @throws IOException - * if an I/O error occurs - * @throws IllegalArgumentException - * if any parameter but that last contains a space character - */ - public void sendCommand(String command, String... parameters) throws IOException, IllegalArgumentException { - StringBuilder commandBuilder = new StringBuilder(); - - commandBuilder.append(command); - for (int parameterIndex = 0; parameterIndex < parameters.length; ++parameterIndex) { - String parameter = parameters[parameterIndex]; - /* space is only allowed in the last parameter. */ - commandBuilder.append(' '); - if (parameter.contains(" ")) { - if (parameterIndex == (parameters.length - 1)) { - commandBuilder.append(':'); - } else { - throw new IllegalArgumentException(String.format("parameter “%s” must not contain space!", parameter)); - } - } - commandBuilder.append(parameter); - } - - logger.finest(String.format(">> %s", commandBuilder)); - outputStream.write((commandBuilder.toString() + "\r\n").getBytes("UTF-8")); - outputStream.flush(); - } - - /** - * Reads a line of reply from the connection. - * - * @return The reply - * @throws IOException - * if an I/O error occurs - * @throws EOFException - * if EOF was reached - */ - public Reply readReply() throws IOException, EOFException { - String line = inputStreamReader.readLine(); - if (line == null) { - throw new EOFException(); - } - - return Reply.parseLine(line); - } - - // - // CLOSEABLE METHODS - // + /** + * Sends a DCC RESUME request to the given recipient. + * + * @param recipient + * The recipient of the request + * @param filename + * The name of the file to resume + * @param port + * The port number from the original DCC SEND request + * @param position + * The position at which to resume the transfer + * @throws IOException + * if an I/O error occurs + */ + void sendDccResume(String recipient, String filename, int port, long position) + throws IOException; - @Override - public void close() throws IOException { - Closeables.close(outputStream, true); - Closeables.close(inputStreamReader, true); - } + /** + * Opens the connection and starts processing messages from the other side. + */ + void open(); - } + /** + * Closes this connection. + * + * @throws IOException + * if an I/O error occurs + */ + void close() throws IOException; }