♻️ Turn FcpConnection into an interface
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Tue, 7 Feb 2023 15:40:16 +0000 (16:40 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Tue, 7 Feb 2023 15:40:16 +0000 (16:40 +0100)
This will break all existing clients which is why this happens on a new branch
that will eventually turn into version 0.2.

src/main/java/net/pterodactylus/fcp/DefaultFcpConnection.java [new file with mode: 0644]
src/main/java/net/pterodactylus/fcp/FcpConnection.java
src/main/java/net/pterodactylus/fcp/FcpConnectionHandler.java [deleted file]
src/main/java/net/pterodactylus/fcp/highlevel/FcpClient.java

diff --git a/src/main/java/net/pterodactylus/fcp/DefaultFcpConnection.java b/src/main/java/net/pterodactylus/fcp/DefaultFcpConnection.java
new file mode 100644 (file)
index 0000000..6890df4
--- /dev/null
@@ -0,0 +1,461 @@
+/*
+ * jFCPlib - FcpConnection.java - Copyright © 2008–2023 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.fcp;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.fcp.FcpUtils.TempInputStream;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Default {@link FcpConnection} implementation.
+ */
+public class DefaultFcpConnection implements FcpConnection {
+
+       /** Logger. */
+       private static final Logger logger = Logger.getLogger(DefaultFcpConnection.class.getName());
+
+       /** The default port for FCP v2. */
+       public static final int DEFAULT_PORT = 9481;
+
+       /** Listener management. */
+       private final FcpListenerManager fcpListenerManager = new FcpListenerManager(this);
+
+       /** The address of the node. */
+       private final InetAddress address;
+
+       /** The port number of the node’s FCP port. */
+       private final int port;
+
+       /** The remote socket. */
+       private Socket remoteSocket;
+
+       /** The input stream from the node. */
+       private InputStream remoteInputStream;
+
+       /** The output stream to the node. */
+       private OutputStream remoteOutputStream;
+
+       /** The connection handler. */
+       private FcpConnectionHandler connectionHandler;
+
+       /** Incoming message statistics. */
+       private static final Map<String, Integer> incomingMessageStatistics = Collections.synchronizedMap(new HashMap<>());
+
+       /**
+        * Creates a new FCP connection to the freenet node running on localhost,
+        * using the default port.
+        *
+        * @throws UnknownHostException
+        *             if the hostname can not be resolved
+        */
+       public DefaultFcpConnection() throws UnknownHostException {
+               this(InetAddress.getLocalHost());
+       }
+
+       /**
+        * Creates a new FCP connection to the Freenet node running on the given
+        * host, listening on the default port.
+        *
+        * @param host
+        *            The hostname of the Freenet node
+        * @throws UnknownHostException
+        *             if <code>host</code> can not be resolved
+        */
+       public DefaultFcpConnection(String host) throws UnknownHostException {
+               this(host, DEFAULT_PORT);
+       }
+
+       /**
+        * Creates a new FCP connection to the Freenet node running on the given
+        * host, listening on the given port.
+        *
+        * @param host
+        *            The hostname of the Freenet node
+        * @param port
+        *            The port number of the node’s FCP port
+        * @throws UnknownHostException
+        *             if <code>host</code> can not be resolved
+        */
+       public DefaultFcpConnection(String host, int port) throws UnknownHostException {
+               this(InetAddress.getByName(host), port);
+       }
+
+       /**
+        * Creates a new FCP connection to the Freenet node running at the given
+        * address, listening on the default port.
+        *
+        * @param address
+        *            The address of the Freenet node
+        */
+       public DefaultFcpConnection(InetAddress address) {
+               this(address, DEFAULT_PORT);
+       }
+
+       /**
+        * Creates a new FCP connection to the Freenet node running at the given
+        * address, listening on the given port.
+        *
+        * @param address
+        *            The address of the Freenet node
+        * @param port
+        *            The port number of the node’s FCP port
+        */
+       public DefaultFcpConnection(InetAddress address, int port) {
+               this.address = address;
+               this.port = port;
+       }
+
+       //
+       // LISTENER MANAGEMENT
+       //
+
+       @Override
+       public void addFcpListener(FcpListener fcpListener) {
+               fcpListenerManager.addListener(fcpListener);
+       }
+
+       @Override
+       public void removeFcpListener(FcpListener fcpListener) {
+               fcpListenerManager.removeListener(fcpListener);
+       }
+
+       @Override
+       public synchronized boolean isClosed() {
+               return connectionHandler == null;
+       }
+
+       //
+       // ACTIONS
+       //
+
+       @Override
+       public synchronized void connect() throws IOException, IllegalStateException {
+               if (connectionHandler != null) {
+                       throw new IllegalStateException("already connected, disconnect first");
+               }
+               logger.info("connecting to " + address + ":" + port + "…");
+               remoteSocket = new Socket(address, port);
+               remoteInputStream = remoteSocket.getInputStream();
+               remoteOutputStream = remoteSocket.getOutputStream();
+               new Thread(connectionHandler = new FcpConnectionHandler(remoteInputStream)).start();
+       }
+
+       @Override
+       @Deprecated
+       public synchronized void disconnect() {
+               close();
+       }
+
+       @Override
+       public void close() {
+               handleDisconnect(null);
+       }
+
+       @Override
+       public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
+               logger.fine("sending message: " + fcpMessage.getName());
+               fcpMessage.write(remoteOutputStream);
+       }
+
+       //
+       // PACKAGE-PRIVATE METHODS
+       //
+
+       /**
+        * Handles the given message, notifying listeners. This message should only
+        * be called by {@link FcpConnectionHandler}.
+        *
+        * @param fcpMessage
+        *            The received message
+        */
+       private void handleMessage(FcpMessage fcpMessage) throws IOException{
+               logger.fine("received message: " + fcpMessage.getName());
+               String messageName = fcpMessage.getName();
+               countMessage(messageName);
+               if ("SimpleProgress".equals(messageName)) {
+                       fcpListenerManager.fireReceivedSimpleProgress(new SimpleProgress(fcpMessage));
+               } else if ("ProtocolError".equals(messageName)) {
+                       fcpListenerManager.fireReceivedProtocolError(new ProtocolError(fcpMessage));
+               } else if ("PersistentGet".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPersistentGet(new PersistentGet(fcpMessage));
+               } else if ("PersistentPut".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPersistentPut(new PersistentPut(fcpMessage));
+               } else if ("PersistentPutDir".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPersistentPutDir(new PersistentPutDir(fcpMessage));
+               } else if ("URIGenerated".equals(messageName)) {
+                       fcpListenerManager.fireReceivedURIGenerated(new URIGenerated(fcpMessage));
+               } else if ("EndListPersistentRequests".equals(messageName)) {
+                       fcpListenerManager.fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
+               } else if ("Peer".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPeer(new Peer(fcpMessage));
+               } else if ("PeerNote".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPeerNote(new PeerNote(fcpMessage));
+               } else if ("StartedCompression".equals(messageName)) {
+                       fcpListenerManager.fireReceivedStartedCompression(new StartedCompression(fcpMessage));
+               } else if ("FinishedCompression".equals(messageName)) {
+                       fcpListenerManager.fireReceivedFinishedCompression(new FinishedCompression(fcpMessage));
+               } else if ("GetFailed".equals(messageName)) {
+                       fcpListenerManager.fireReceivedGetFailed(new GetFailed(fcpMessage));
+               } else if ("PutFetchable".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPutFetchable(new PutFetchable(fcpMessage));
+               } else if ("PutSuccessful".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPutSuccessful(new PutSuccessful(fcpMessage));
+               } else if ("PutFailed".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPutFailed(new PutFailed(fcpMessage));
+               } else if ("DataFound".equals(messageName)) {
+                       fcpListenerManager.fireReceivedDataFound(new DataFound(fcpMessage));
+               } else if ("SubscribedUSKUpdate".equals(messageName)) {
+                       fcpListenerManager.fireReceivedSubscribedUSKUpdate(new SubscribedUSKUpdate(fcpMessage));
+               } else if ("SubscribedUSK".equals(messageName)) {
+                       fcpListenerManager.fireReceivedSubscribedUSK(new SubscribedUSK(fcpMessage));
+               } else if ("IdentifierCollision".equals(messageName)) {
+                       fcpListenerManager.fireReceivedIdentifierCollision(new IdentifierCollision(fcpMessage));
+               } else if ("AllData".equals(messageName)) {
+                       InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength")));
+                       fcpListenerManager.fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
+               } else if ("EndListPeerNotes".equals(messageName)) {
+                       fcpListenerManager.fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
+               } else if ("EndListPeers".equals(messageName)) {
+                       fcpListenerManager.fireReceivedEndListPeers(new EndListPeers(fcpMessage));
+               } else if ("SSKKeypair".equals(messageName)) {
+                       fcpListenerManager.fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
+               } else if ("PeerRemoved".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
+               } else if ("PersistentRequestModified".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPersistentRequestModified(new PersistentRequestModified(fcpMessage));
+               } else if ("PersistentRequestRemoved".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPersistentRequestRemoved(new PersistentRequestRemoved(fcpMessage));
+               } else if ("UnknownPeerNoteType".equals(messageName)) {
+                       fcpListenerManager.fireReceivedUnknownPeerNoteType(new UnknownPeerNoteType(fcpMessage));
+               } else if ("UnknownNodeIdentifier".equals(messageName)) {
+                       fcpListenerManager.fireReceivedUnknownNodeIdentifier(new UnknownNodeIdentifier(fcpMessage));
+               } else if ("FCPPluginReply".equals(messageName)) {
+                       InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength"), 0));
+                       fcpListenerManager.fireReceivedFCPPluginReply(new FCPPluginReply(fcpMessage, payloadInputStream));
+               } else if ("PluginInfo".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPluginInfo(new PluginInfo(fcpMessage));
+               } else if ("PluginRemoved".equals(messageName)) {
+                       fcpListenerManager.fireReceivedPluginRemoved(new PluginRemoved(fcpMessage));
+               } else if ("NodeData".equals(messageName)) {
+                       fcpListenerManager.fireReceivedNodeData(new NodeData(fcpMessage));
+               } else if ("TestDDAReply".equals(messageName)) {
+                       fcpListenerManager.fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
+               } else if ("TestDDAComplete".equals(messageName)) {
+                       fcpListenerManager.fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
+               } else if ("ConfigData".equals(messageName)) {
+                       fcpListenerManager.fireReceivedConfigData(new ConfigData(fcpMessage));
+               } else if ("NodeHello".equals(messageName)) {
+                       fcpListenerManager.fireReceivedNodeHello(new NodeHello(fcpMessage));
+               } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
+                       fcpListenerManager.fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
+               } else if ("SentFeed".equals(messageName)) {
+                       fcpListenerManager.fireSentFeed(new SentFeed(fcpMessage));
+               } else if ("ReceivedBookmarkFeed".equals(messageName)) {
+                       fcpListenerManager.fireReceivedBookmarkFeed(new ReceivedBookmarkFeed(fcpMessage));
+               } else {
+                       fcpListenerManager.fireMessageReceived(fcpMessage);
+               }
+       }
+
+       /**
+        * Handles a disconnect from the node.
+        *
+        * @param throwable
+        *            The exception that caused the disconnect, or
+        *            <code>null</code> if there was no exception
+        */
+        private synchronized void handleDisconnect(Throwable throwable) {
+               FcpUtils.close(remoteInputStream);
+               FcpUtils.close(remoteOutputStream);
+               FcpUtils.close(remoteSocket);
+               if (connectionHandler != null) {
+                       connectionHandler.stop();
+                       connectionHandler = null;
+                       fcpListenerManager.fireConnectionClosed(throwable);
+               }
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Incremets the counter in {@link #incomingMessageStatistics} by
+        * <cod>1</code> for the given message name.
+        *
+        * @param name
+        *            The name of the message to count
+        */
+       private void countMessage(String name) {
+               int oldValue = 0;
+               if (incomingMessageStatistics.containsKey(name)) {
+                       oldValue = incomingMessageStatistics.get(name);
+               }
+               incomingMessageStatistics.put(name, oldValue + 1);
+               logger.finest("count for " + name + ": " + (oldValue + 1));
+       }
+
+       private synchronized InputStream getInputStream(long dataLength) throws IOException {
+               return new TempInputStream(remoteInputStream, dataLength);
+       }
+
+       /**
+        * Handles an FCP connection to a node.
+        */
+       class FcpConnectionHandler implements Runnable {
+
+               /** The logger. */
+               private final Logger logger = Logger.getLogger(FcpConnectionHandler.class.getName());
+
+               /** The input stream from the node. */
+               private final InputStream remoteInputStream;
+
+               /** Whether to stop the connection handler. */
+               private boolean shouldStop;
+
+               /** Whether the next read line feed should be ignored. */
+               private boolean ignoreNextLinefeed;
+
+               /**
+                * Creates a new connection handler that operates on the given input stream.
+                *
+                * @param remoteInputStream
+                *            The input stream from the node
+                */
+               public FcpConnectionHandler(InputStream remoteInputStream) {
+                       this.remoteInputStream = remoteInputStream;
+               }
+
+               /**
+                * {@inheritDoc}
+                */
+               @Override
+               public void run() {
+                       FcpMessage fcpMessage = null;
+                       Throwable throwable = null;
+                       while (true) {
+                               synchronized (this) {
+                                       if (shouldStop) {
+                                               break;
+                                       }
+                               }
+                               try {
+                                       String line = readLine();
+                                       logger.log(Level.FINEST, String.format("read line: %1$s", line));
+                                       if (line == null) {
+                                               throwable = new EOFException();
+                                               break;
+                                       }
+                                       if (line.length() == 0) {
+                                               continue;
+                                       }
+                                       line = line.trim();
+                                       if (fcpMessage == null) {
+                                               fcpMessage = new FcpMessage(line);
+                                               continue;
+                                       }
+                                       if ("EndMessage".equalsIgnoreCase(line) || "Data".equalsIgnoreCase(line)) {
+                                               handleMessage(fcpMessage);
+                                               fcpMessage = null;
+                                       }
+                                       int equalSign = line.indexOf('=');
+                                       if (equalSign == -1) {
+                                               /* something's fishy! */
+                                               continue;
+                                       }
+                                       String field = line.substring(0, equalSign);
+                                       String value = line.substring(equalSign + 1);
+                                       assert fcpMessage != null: "fcp message is null";
+                                       fcpMessage.setField(field, value);
+                               } catch (IOException ioe1) {
+                                       throwable = ioe1;
+                                       break;
+                               }
+                       }
+                       handleDisconnect(throwable);
+               }
+
+               /**
+                * Stops the connection handler.
+                */
+               public void stop() {
+                       synchronized (this) {
+                               shouldStop = true;
+                       }
+               }
+
+               //
+               // PRIVATE METHODS
+               //
+
+               /**
+                * Reads bytes from {@link #remoteInputStream} until ‘\r’ or ‘\n’ are
+                * encountered and decodes the read bytes using UTF-8.
+                *
+                * @return The decoded line
+                * @throws IOException
+                *             if an I/O error occurs
+                */
+               private String readLine() throws IOException {
+                       byte[] readBytes = new byte[512];
+                       int readIndex = 0;
+                       while (true) {
+                               int nextByte = remoteInputStream.read();
+                               if (nextByte == -1) {
+                                       if (readIndex == 0) {
+                                               return null;
+                                       }
+                                       break;
+                               }
+                               if (nextByte == 10) {
+                                       if (!ignoreNextLinefeed) {
+                                               break;
+                                       }
+                               }
+                               ignoreNextLinefeed = false;
+                               if (nextByte == 13) {
+                                       ignoreNextLinefeed = true;
+                                       break;
+                               }
+                               if (readIndex == readBytes.length) {
+                                       /* recopy & enlarge array */
+                                       byte[] newReadBytes = new byte[readBytes.length * 2];
+                                       System.arraycopy(readBytes, 0, newReadBytes, 0, readBytes.length);
+                                       readBytes = newReadBytes;
+                               }
+                               readBytes[readIndex++] = (byte) nextByte;
+                       }
+                       return new String(readBytes, UTF_8);
+               }
+
+       }
+
+}
index 3331bdf..a26e3e8 100644 (file)
-/*
- * jFCPlib - FcpConnection.java - Copyright © 2008–2016 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 <http://www.gnu.org/licenses/>.
- */
-
 package net.pterodactylus.fcp;
 
 import java.io.Closeable;
-import java.io.FilterInputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.logging.Logger;
-
-import net.pterodactylus.fcp.FcpUtils.TempInputStream;
 
 /**
  * An FCP connection to a Freenet node.
- *
- * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
  */
-public class FcpConnection implements Closeable {
-
-       /** Logger. */
-       private static final Logger logger = Logger.getLogger(FcpConnection.class.getName());
-
-       /** The default port for FCP v2. */
-       public static final int DEFAULT_PORT = 9481;
-
-       /** Listener management. */
-       private final FcpListenerManager fcpListenerManager = new FcpListenerManager(this);
-
-       /** The address of the node. */
-       private final InetAddress address;
-
-       /** The port number of the node’s FCP port. */
-       private final int port;
-
-       /** The remote socket. */
-       private Socket remoteSocket;
-
-       /** The input stream from the node. */
-       private InputStream remoteInputStream;
-
-       /** The output stream to the node. */
-       private OutputStream remoteOutputStream;
-
-       /** The connection handler. */
-       private FcpConnectionHandler connectionHandler;
-
-       /** Incoming message statistics. */
-       private static final Map<String, Integer> incomingMessageStatistics = Collections.synchronizedMap(new HashMap<String, Integer>());
+public interface FcpConnection extends Closeable {
 
        /**
-        * Creates a new FCP connection to the freenet node running on localhost,
-        * using the default port.
-        *
-        * @throws UnknownHostException
-        *             if the hostname can not be resolved
-        */
-       public FcpConnection() throws UnknownHostException {
-               this(InetAddress.getLocalHost());
-       }
-
-       /**
-        * Creates a new FCP connection to the Freenet node running on the given
-        * host, listening on the default port.
-        *
-        * @param host
-        *            The hostname of the Freenet node
-        * @throws UnknownHostException
-        *             if <code>host</code> can not be resolved
-        */
-       public FcpConnection(String host) throws UnknownHostException {
-               this(host, DEFAULT_PORT);
-       }
-
-       /**
-        * Creates a new FCP connection to the Freenet node running on the given
-        * host, listening on the given port.
-        *
-        * @param host
-        *            The hostname of the Freenet node
-        * @param port
-        *            The port number of the node’s FCP port
-        * @throws UnknownHostException
-        *             if <code>host</code> can not be resolved
-        */
-       public FcpConnection(String host, int port) throws UnknownHostException {
-               this(InetAddress.getByName(host), port);
-       }
-
-       /**
-        * Creates a new FCP connection to the Freenet node running at the given
-        * address, listening on the default port.
-        *
-        * @param address
-        *            The address of the Freenet node
-        */
-       public FcpConnection(InetAddress address) {
-               this(address, DEFAULT_PORT);
-       }
-
-       /**
-        * Creates a new FCP connection to the Freenet node running at the given
-        * address, listening on the given port.
+        * Adds the given listener to the list of listeners.
         *
-        * @param address
-        *            The address of the Freenet node
-        * @param port
-        *            The port number of the node’s FCP port
+        * @param fcpListener The listener to add
         */
-       public FcpConnection(InetAddress address, int port) {
-               this.address = address;
-               this.port = port;
-       }
-
-       //
-       // LISTENER MANAGEMENT
-       //
+       void addFcpListener(FcpListener fcpListener);
 
        /**
-        * Adds the given listener to the list of listeners.
+        * Removes the given listener from the list of listeners.
         *
-        * @param fcpListener
-        *            The listener to add
+        * @param fcpListener The listener to remove
         */
-       public void addFcpListener(FcpListener fcpListener) {
-               fcpListenerManager.addListener(fcpListener);
-       }
+       void removeFcpListener(FcpListener fcpListener);
 
        /**
-        * Removes the given listener from the list of listeners.
+        * Returns whether this connection has been closed, either by {@link #close()} or by the remote side.
         *
-        * @param fcpListener
-        *            The listener to remove
+        * @return {@code true} if this connection is closed, {@code false} otherwise
         */
-       public void removeFcpListener(FcpListener fcpListener) {
-               fcpListenerManager.removeListener(fcpListener);
-       }
-
-       public synchronized boolean isClosed() {
-               return connectionHandler == null;
-       }
-
-       //
-       // ACTIONS
-       //
+       boolean isClosed();
 
        /**
         * Connects to the node.
         *
-        * @throws IOException
-        *             if an I/O error occurs
-        * @throws IllegalStateException
-        *             if there is already a connection to the node
+        * @throws IOException           if an I/O error occurs
+        * @throws IllegalStateException if there is already a connection to the node
         */
-       public synchronized void connect() throws IOException, IllegalStateException {
-               if (connectionHandler != null) {
-                       throw new IllegalStateException("already connected, disconnect first");
-               }
-               logger.info("connecting to " + address + ":" + port + "…");
-               remoteSocket = new Socket(address, port);
-               remoteInputStream = remoteSocket.getInputStream();
-               remoteOutputStream = remoteSocket.getOutputStream();
-               new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
-       }
+       void connect() throws IOException, IllegalStateException;
 
        /**
         * Disconnects from the node. If there is no connection to the node, this
@@ -191,172 +44,21 @@ public class FcpConnection implements Closeable {
         * @deprecated Use {@link #close()} instead
         */
        @Deprecated
-       public synchronized void disconnect() {
-               close();
-       }
+       void disconnect();
 
        /**
         * Closes the connection. If there is no connection to the node, this
         * method does nothing.
         */
        @Override
-       public void close() {
-               handleDisconnect(null);
-       }
+       void close();
 
        /**
         * Sends the given FCP message.
         *
-        * @param fcpMessage
-        *            The FCP message to send
-        * @throws IOException
-        *             if an I/O error occurs
+        * @param fcpMessage The FCP message to send
+        * @throws IOException if an I/O error occurs
         */
-       public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
-               logger.fine("sending message: " + fcpMessage.getName());
-               fcpMessage.write(remoteOutputStream);
-       }
-
-       //
-       // PACKAGE-PRIVATE METHODS
-       //
-
-       /**
-        * Handles the given message, notifying listeners. This message should only
-        * be called by {@link FcpConnectionHandler}.
-        *
-        * @param fcpMessage
-        *            The received message
-        */
-       void handleMessage(FcpMessage fcpMessage) throws IOException{
-               logger.fine("received message: " + fcpMessage.getName());
-               String messageName = fcpMessage.getName();
-               countMessage(messageName);
-               if ("SimpleProgress".equals(messageName)) {
-                       fcpListenerManager.fireReceivedSimpleProgress(new SimpleProgress(fcpMessage));
-               } else if ("ProtocolError".equals(messageName)) {
-                       fcpListenerManager.fireReceivedProtocolError(new ProtocolError(fcpMessage));
-               } else if ("PersistentGet".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPersistentGet(new PersistentGet(fcpMessage));
-               } else if ("PersistentPut".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPersistentPut(new PersistentPut(fcpMessage));
-               } else if ("PersistentPutDir".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPersistentPutDir(new PersistentPutDir(fcpMessage));
-               } else if ("URIGenerated".equals(messageName)) {
-                       fcpListenerManager.fireReceivedURIGenerated(new URIGenerated(fcpMessage));
-               } else if ("EndListPersistentRequests".equals(messageName)) {
-                       fcpListenerManager.fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
-               } else if ("Peer".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPeer(new Peer(fcpMessage));
-               } else if ("PeerNote".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPeerNote(new PeerNote(fcpMessage));
-               } else if ("StartedCompression".equals(messageName)) {
-                       fcpListenerManager.fireReceivedStartedCompression(new StartedCompression(fcpMessage));
-               } else if ("FinishedCompression".equals(messageName)) {
-                       fcpListenerManager.fireReceivedFinishedCompression(new FinishedCompression(fcpMessage));
-               } else if ("GetFailed".equals(messageName)) {
-                       fcpListenerManager.fireReceivedGetFailed(new GetFailed(fcpMessage));
-               } else if ("PutFetchable".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPutFetchable(new PutFetchable(fcpMessage));
-               } else if ("PutSuccessful".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPutSuccessful(new PutSuccessful(fcpMessage));
-               } else if ("PutFailed".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPutFailed(new PutFailed(fcpMessage));
-               } else if ("DataFound".equals(messageName)) {
-                       fcpListenerManager.fireReceivedDataFound(new DataFound(fcpMessage));
-               } else if ("SubscribedUSKUpdate".equals(messageName)) {
-                       fcpListenerManager.fireReceivedSubscribedUSKUpdate(new SubscribedUSKUpdate(fcpMessage));
-               } else if ("SubscribedUSK".equals(messageName)) {
-                       fcpListenerManager.fireReceivedSubscribedUSK(new SubscribedUSK(fcpMessage));
-               } else if ("IdentifierCollision".equals(messageName)) {
-                       fcpListenerManager.fireReceivedIdentifierCollision(new IdentifierCollision(fcpMessage));
-               } else if ("AllData".equals(messageName)) {
-                       InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength")));
-                       fcpListenerManager.fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
-               } else if ("EndListPeerNotes".equals(messageName)) {
-                       fcpListenerManager.fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
-               } else if ("EndListPeers".equals(messageName)) {
-                       fcpListenerManager.fireReceivedEndListPeers(new EndListPeers(fcpMessage));
-               } else if ("SSKKeypair".equals(messageName)) {
-                       fcpListenerManager.fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
-               } else if ("PeerRemoved".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
-               } else if ("PersistentRequestModified".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPersistentRequestModified(new PersistentRequestModified(fcpMessage));
-               } else if ("PersistentRequestRemoved".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPersistentRequestRemoved(new PersistentRequestRemoved(fcpMessage));
-               } else if ("UnknownPeerNoteType".equals(messageName)) {
-                       fcpListenerManager.fireReceivedUnknownPeerNoteType(new UnknownPeerNoteType(fcpMessage));
-               } else if ("UnknownNodeIdentifier".equals(messageName)) {
-                       fcpListenerManager.fireReceivedUnknownNodeIdentifier(new UnknownNodeIdentifier(fcpMessage));
-               } else if ("FCPPluginReply".equals(messageName)) {
-                       InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength"), 0));
-                       fcpListenerManager.fireReceivedFCPPluginReply(new FCPPluginReply(fcpMessage, payloadInputStream));
-               } else if ("PluginInfo".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPluginInfo(new PluginInfo(fcpMessage));
-               } else if ("PluginRemoved".equals(messageName)) {
-                       fcpListenerManager.fireReceivedPluginRemoved(new PluginRemoved(fcpMessage));
-               } else if ("NodeData".equals(messageName)) {
-                       fcpListenerManager.fireReceivedNodeData(new NodeData(fcpMessage));
-               } else if ("TestDDAReply".equals(messageName)) {
-                       fcpListenerManager.fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
-               } else if ("TestDDAComplete".equals(messageName)) {
-                       fcpListenerManager.fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
-               } else if ("ConfigData".equals(messageName)) {
-                       fcpListenerManager.fireReceivedConfigData(new ConfigData(fcpMessage));
-               } else if ("NodeHello".equals(messageName)) {
-                       fcpListenerManager.fireReceivedNodeHello(new NodeHello(fcpMessage));
-               } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
-                       fcpListenerManager.fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
-               } else if ("SentFeed".equals(messageName)) {
-                       fcpListenerManager.fireSentFeed(new SentFeed(fcpMessage));
-               } else if ("ReceivedBookmarkFeed".equals(messageName)) {
-                       fcpListenerManager.fireReceivedBookmarkFeed(new ReceivedBookmarkFeed(fcpMessage));
-               } else {
-                       fcpListenerManager.fireMessageReceived(fcpMessage);
-               }
-       }
-
-       /**
-        * Handles a disconnect from the node.
-        *
-        * @param throwable
-        *            The exception that caused the disconnect, or
-        *            <code>null</code> if there was no exception
-        */
-       synchronized void handleDisconnect(Throwable throwable) {
-               FcpUtils.close(remoteInputStream);
-               FcpUtils.close(remoteOutputStream);
-               FcpUtils.close(remoteSocket);
-               if (connectionHandler != null) {
-                       connectionHandler.stop();
-                       connectionHandler = null;
-                       fcpListenerManager.fireConnectionClosed(throwable);
-               }
-       }
-
-       //
-       // PRIVATE METHODS
-       //
-
-       /**
-        * Incremets the counter in {@link #incomingMessageStatistics} by
-        * <cod>1</code> for the given message name.
-        *
-        * @param name
-        *            The name of the message to count
-        */
-       private void countMessage(String name) {
-               int oldValue = 0;
-               if (incomingMessageStatistics.containsKey(name)) {
-                       oldValue = incomingMessageStatistics.get(name);
-               }
-               incomingMessageStatistics.put(name, oldValue + 1);
-               logger.finest("count for " + name + ": " + (oldValue + 1));
-       }
-
-       private synchronized InputStream getInputStream(long dataLength) throws IOException {
-               return new TempInputStream(remoteInputStream, dataLength);
-       }
+       void sendMessage(FcpMessage fcpMessage) throws IOException;
 
 }
diff --git a/src/main/java/net/pterodactylus/fcp/FcpConnectionHandler.java b/src/main/java/net/pterodactylus/fcp/FcpConnectionHandler.java
deleted file mode 100644 (file)
index aa897e2..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * jFCPlib - FcpConnectionHandler.java - Copyright © 2008–2016 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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.fcp;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Handles an FCP connection to a node.
- *
- * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
- */
-class FcpConnectionHandler implements Runnable {
-
-       /** The logger. */
-       private static final Logger logger = Logger.getLogger(FcpConnectionHandler.class.getName());
-
-       /** The underlying connection. */
-       private final FcpConnection fcpConnection;
-
-       /** The input stream from the node. */
-       private final InputStream remoteInputStream;
-
-       /** Whether to stop the connection handler. */
-       private boolean shouldStop;
-
-       /** Whether the next read line feed should be ignored. */
-       private boolean ignoreNextLinefeed;
-
-       /**
-        * Creates a new connection handler that operates on the given connection
-        * and input stream.
-        *
-        * @param fcpConnection
-        *            The underlying FCP connection
-        * @param remoteInputStream
-        *            The input stream from the node
-        */
-       public FcpConnectionHandler(FcpConnection fcpConnection, InputStream remoteInputStream) {
-               this.fcpConnection = fcpConnection;
-               this.remoteInputStream = remoteInputStream;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void run() {
-               FcpMessage fcpMessage = null;
-               Throwable throwable = null;
-               while (true) {
-                       synchronized (this) {
-                               if (shouldStop) {
-                                       break;
-                               }
-                       }
-                       try {
-                               String line = readLine();
-                               logger.log(Level.FINEST, String.format("read line: %1$s", line));
-                               if (line == null) {
-                                       throwable = new EOFException();
-                                       break;
-                               }
-                               if (line.length() == 0) {
-                                       continue;
-                               }
-                               line = line.trim();
-                               if (fcpMessage == null) {
-                                       fcpMessage = new FcpMessage(line);
-                                       continue;
-                               }
-                               if ("EndMessage".equalsIgnoreCase(line) || "Data".equalsIgnoreCase(line)) {
-                                       fcpConnection.handleMessage(fcpMessage);
-                                       fcpMessage = null;
-                               }
-                               int equalSign = line.indexOf('=');
-                               if (equalSign == -1) {
-                                       /* something's fishy! */
-                                       continue;
-                               }
-                               String field = line.substring(0, equalSign);
-                               String value = line.substring(equalSign + 1);
-                               assert fcpMessage != null: "fcp message is null";
-                               fcpMessage.setField(field, value);
-                       } catch (IOException ioe1) {
-                               throwable = ioe1;
-                               break;
-                       }
-               }
-               fcpConnection.handleDisconnect(throwable);
-       }
-
-       /**
-        * Stops the connection handler.
-        */
-       public void stop() {
-               synchronized (this) {
-                       shouldStop = true;
-               }
-       }
-
-       //
-       // PRIVATE METHODS
-       //
-
-       /**
-        * Reads bytes from {@link #remoteInputStream} until ‘\r’ or ‘\n’ are
-        * encountered and decodes the read bytes using UTF-8.
-        *
-        * @return The decoded line
-        * @throws IOException
-        *             if an I/O error occurs
-        */
-       private String readLine() throws IOException {
-               byte[] readBytes = new byte[512];
-               int readIndex = 0;
-               while (true) {
-                       int nextByte = remoteInputStream.read();
-                       if (nextByte == -1) {
-                               if (readIndex == 0) {
-                                       return null;
-                               }
-                               break;
-                       }
-                       if (nextByte == 10) {
-                               if (!ignoreNextLinefeed) {
-                                       break;
-                               }
-                       }
-                       ignoreNextLinefeed = false;
-                       if (nextByte == 13) {
-                               ignoreNextLinefeed = true;
-                               break;
-                       }
-                       if (readIndex == readBytes.length) {
-                               /* recopy & enlarge array */
-                               byte[] newReadBytes = new byte[readBytes.length * 2];
-                               System.arraycopy(readBytes, 0, newReadBytes, 0, readBytes.length);
-                               readBytes = newReadBytes;
-                       }
-                       readBytes[readIndex++] = (byte) nextByte;
-               }
-               ByteBuffer byteBuffer = ByteBuffer.wrap(readBytes, 0, readIndex);
-               return Charset.forName("UTF-8").decode(byteBuffer).toString();
-       }
-
-}
index 473fe98..ced9995 100644 (file)
@@ -43,6 +43,7 @@ import net.pterodactylus.fcp.ClientHello;
 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
 import net.pterodactylus.fcp.ConfigData;
 import net.pterodactylus.fcp.DataFound;
+import net.pterodactylus.fcp.DefaultFcpConnection;
 import net.pterodactylus.fcp.EndListPeerNotes;
 import net.pterodactylus.fcp.EndListPeers;
 import net.pterodactylus.fcp.EndListPersistentRequests;
@@ -123,7 +124,7 @@ public class FcpClient implements Closeable {
         *             if the given hostname can not be resolved
         */
        public FcpClient(String hostname) throws UnknownHostException {
-               this(hostname, FcpConnection.DEFAULT_PORT);
+               this(hostname, DefaultFcpConnection.DEFAULT_PORT);
        }
 
        /**
@@ -147,7 +148,7 @@ public class FcpClient implements Closeable {
         *            The host address of the Freenet node
         */
        public FcpClient(InetAddress host) {
-               this(host, FcpConnection.DEFAULT_PORT);
+               this(host, DefaultFcpConnection.DEFAULT_PORT);
        }
 
        /**
@@ -159,7 +160,7 @@ public class FcpClient implements Closeable {
         *            The Freenet node’s FCP port
         */
        public FcpClient(InetAddress host, int port) {
-               this(new FcpConnection(host, port), false);
+               this(new DefaultFcpConnection(host, port), false);
        }
 
        /**