2 * jFCPlib - FpcConnection.java - Copyright © 2008 David Roden
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 package net.pterodactylus.fcp;
21 import java.io.Closeable;
22 import java.io.FilterInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.net.InetAddress;
27 import java.net.Socket;
28 import java.net.UnknownHostException;
29 import java.util.Collections;
30 import java.util.HashMap;
32 import java.util.logging.Logger;
34 import net.pterodactylus.fcp.FcpUtils.TempInputStream;
37 * An FCP connection to a Freenet node.
39 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
41 public class FcpConnection implements Closeable {
44 private static final Logger logger = Logger.getLogger(FcpConnection.class.getName());
46 /** The default port for FCP v2. */
47 public static final int DEFAULT_PORT = 9481;
49 /** Listener management. */
50 private final FcpListenerManager fcpListenerManager = new FcpListenerManager(this);
52 /** The address of the node. */
53 private final InetAddress address;
55 /** The port number of the node’s FCP port. */
56 private final int port;
58 /** The remote socket. */
59 private Socket remoteSocket;
61 /** The input stream from the node. */
62 private InputStream remoteInputStream;
64 /** The output stream to the node. */
65 private OutputStream remoteOutputStream;
67 /** The connection handler. */
68 private FcpConnectionHandler connectionHandler;
70 /** Incoming message statistics. */
71 private static final Map<String, Integer> incomingMessageStatistics = Collections.synchronizedMap(new HashMap<String, Integer>());
74 * Creates a new FCP connection to the freenet node running on localhost,
75 * using the default port.
77 * @throws UnknownHostException
78 * if the hostname can not be resolved
80 public FcpConnection() throws UnknownHostException {
81 this(InetAddress.getLocalHost());
85 * Creates a new FCP connection to the Freenet node running on the given
86 * host, listening on the default port.
89 * The hostname of the Freenet node
90 * @throws UnknownHostException
91 * if <code>host</code> can not be resolved
93 public FcpConnection(String host) throws UnknownHostException {
94 this(host, DEFAULT_PORT);
98 * Creates a new FCP connection to the Freenet node running on the given
99 * host, listening on the given port.
102 * The hostname of the Freenet node
104 * The port number of the node’s FCP port
105 * @throws UnknownHostException
106 * if <code>host</code> can not be resolved
108 public FcpConnection(String host, int port) throws UnknownHostException {
109 this(InetAddress.getByName(host), port);
113 * Creates a new FCP connection to the Freenet node running at the given
114 * address, listening on the default port.
117 * The address of the Freenet node
119 public FcpConnection(InetAddress address) {
120 this(address, DEFAULT_PORT);
124 * Creates a new FCP connection to the Freenet node running at the given
125 * address, listening on the given port.
128 * The address of the Freenet node
130 * The port number of the node’s FCP port
132 public FcpConnection(InetAddress address, int port) {
133 this.address = address;
138 // LISTENER MANAGEMENT
142 * Adds the given listener to the list of listeners.
145 * The listener to add
147 public void addFcpListener(FcpListener fcpListener) {
148 fcpListenerManager.addListener(fcpListener);
152 * Removes the given listener from the list of listeners.
155 * The listener to remove
157 public void removeFcpListener(FcpListener fcpListener) {
158 fcpListenerManager.removeListener(fcpListener);
161 public synchronized boolean isClosed() {
162 return connectionHandler == null;
170 * Connects to the node.
172 * @throws IOException
173 * if an I/O error occurs
174 * @throws IllegalStateException
175 * if there is already a connection to the node
177 public synchronized void connect() throws IOException, IllegalStateException {
178 if (connectionHandler != null) {
179 throw new IllegalStateException("already connected, disconnect first");
181 logger.info("connecting to " + address + ":" + port + "…");
182 remoteSocket = new Socket(address, port);
183 remoteInputStream = remoteSocket.getInputStream();
184 remoteOutputStream = remoteSocket.getOutputStream();
185 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
189 * Disconnects from the node. If there is no connection to the node, this
190 * method does nothing.
192 * @deprecated Use {@link #close()} instead
195 public synchronized void disconnect() {
200 * Closes the connection. If there is no connection to the node, this
201 * method does nothing.
204 public void close() {
205 handleDisconnect(null);
209 * Sends the given FCP message.
212 * The FCP message to send
213 * @throws IOException
214 * if an I/O error occurs
216 public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
217 logger.fine("sending message: " + fcpMessage.getName());
218 fcpMessage.write(remoteOutputStream);
222 // PACKAGE-PRIVATE METHODS
226 * Handles the given message, notifying listeners. This message should only
227 * be called by {@link FcpConnectionHandler}.
230 * The received message
232 void handleMessage(FcpMessage fcpMessage) throws IOException{
233 logger.fine("received message: " + fcpMessage.getName());
234 String messageName = fcpMessage.getName();
235 countMessage(messageName);
236 if ("SimpleProgress".equals(messageName)) {
237 fcpListenerManager.fireReceivedSimpleProgress(new SimpleProgress(fcpMessage));
238 } else if ("ProtocolError".equals(messageName)) {
239 fcpListenerManager.fireReceivedProtocolError(new ProtocolError(fcpMessage));
240 } else if ("PersistentGet".equals(messageName)) {
241 fcpListenerManager.fireReceivedPersistentGet(new PersistentGet(fcpMessage));
242 } else if ("PersistentPut".equals(messageName)) {
243 fcpListenerManager.fireReceivedPersistentPut(new PersistentPut(fcpMessage));
244 } else if ("PersistentPutDir".equals(messageName)) {
245 fcpListenerManager.fireReceivedPersistentPutDir(new PersistentPutDir(fcpMessage));
246 } else if ("URIGenerated".equals(messageName)) {
247 fcpListenerManager.fireReceivedURIGenerated(new URIGenerated(fcpMessage));
248 } else if ("EndListPersistentRequests".equals(messageName)) {
249 fcpListenerManager.fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
250 } else if ("Peer".equals(messageName)) {
251 fcpListenerManager.fireReceivedPeer(new Peer(fcpMessage));
252 } else if ("PeerNote".equals(messageName)) {
253 fcpListenerManager.fireReceivedPeerNote(new PeerNote(fcpMessage));
254 } else if ("StartedCompression".equals(messageName)) {
255 fcpListenerManager.fireReceivedStartedCompression(new StartedCompression(fcpMessage));
256 } else if ("FinishedCompression".equals(messageName)) {
257 fcpListenerManager.fireReceivedFinishedCompression(new FinishedCompression(fcpMessage));
258 } else if ("GetFailed".equals(messageName)) {
259 fcpListenerManager.fireReceivedGetFailed(new GetFailed(fcpMessage));
260 } else if ("PutFetchable".equals(messageName)) {
261 fcpListenerManager.fireReceivedPutFetchable(new PutFetchable(fcpMessage));
262 } else if ("PutSuccessful".equals(messageName)) {
263 fcpListenerManager.fireReceivedPutSuccessful(new PutSuccessful(fcpMessage));
264 } else if ("PutFailed".equals(messageName)) {
265 fcpListenerManager.fireReceivedPutFailed(new PutFailed(fcpMessage));
266 } else if ("DataFound".equals(messageName)) {
267 fcpListenerManager.fireReceivedDataFound(new DataFound(fcpMessage));
268 } else if ("SubscribedUSKUpdate".equals(messageName)) {
269 fcpListenerManager.fireReceivedSubscribedUSKUpdate(new SubscribedUSKUpdate(fcpMessage));
270 } else if ("SubscribedUSK".equals(messageName)) {
271 fcpListenerManager.fireReceivedSubscribedUSK(new SubscribedUSK(fcpMessage));
272 } else if ("IdentifierCollision".equals(messageName)) {
273 fcpListenerManager.fireReceivedIdentifierCollision(new IdentifierCollision(fcpMessage));
274 } else if ("AllData".equals(messageName)) {
275 InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength")));
276 fcpListenerManager.fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
277 } else if ("EndListPeerNotes".equals(messageName)) {
278 fcpListenerManager.fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
279 } else if ("EndListPeers".equals(messageName)) {
280 fcpListenerManager.fireReceivedEndListPeers(new EndListPeers(fcpMessage));
281 } else if ("SSKKeypair".equals(messageName)) {
282 fcpListenerManager.fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
283 } else if ("PeerRemoved".equals(messageName)) {
284 fcpListenerManager.fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
285 } else if ("PersistentRequestModified".equals(messageName)) {
286 fcpListenerManager.fireReceivedPersistentRequestModified(new PersistentRequestModified(fcpMessage));
287 } else if ("PersistentRequestRemoved".equals(messageName)) {
288 fcpListenerManager.fireReceivedPersistentRequestRemoved(new PersistentRequestRemoved(fcpMessage));
289 } else if ("UnknownPeerNoteType".equals(messageName)) {
290 fcpListenerManager.fireReceivedUnknownPeerNoteType(new UnknownPeerNoteType(fcpMessage));
291 } else if ("UnknownNodeIdentifier".equals(messageName)) {
292 fcpListenerManager.fireReceivedUnknownNodeIdentifier(new UnknownNodeIdentifier(fcpMessage));
293 } else if ("FCPPluginReply".equals(messageName)) {
294 InputStream payloadInputStream = getInputStream(FcpUtils.safeParseLong(fcpMessage.getField("DataLength")));
295 fcpListenerManager.fireReceivedFCPPluginReply(new FCPPluginReply(fcpMessage, payloadInputStream));
296 } else if ("PluginInfo".equals(messageName)) {
297 fcpListenerManager.fireReceivedPluginInfo(new PluginInfo(fcpMessage));
298 } else if ("PluginRemoved".equals(messageName)) {
299 fcpListenerManager.fireReceivedPluginRemoved(new PluginRemoved(fcpMessage));
300 } else if ("NodeData".equals(messageName)) {
301 fcpListenerManager.fireReceivedNodeData(new NodeData(fcpMessage));
302 } else if ("TestDDAReply".equals(messageName)) {
303 fcpListenerManager.fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
304 } else if ("TestDDAComplete".equals(messageName)) {
305 fcpListenerManager.fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
306 } else if ("ConfigData".equals(messageName)) {
307 fcpListenerManager.fireReceivedConfigData(new ConfigData(fcpMessage));
308 } else if ("NodeHello".equals(messageName)) {
309 fcpListenerManager.fireReceivedNodeHello(new NodeHello(fcpMessage));
310 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
311 fcpListenerManager.fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
312 } else if ("SentFeed".equals(messageName)) {
313 fcpListenerManager.fireSentFeed(new SentFeed(fcpMessage));
314 } else if ("ReceivedBookmarkFeed".equals(messageName)) {
315 fcpListenerManager.fireReceivedBookmarkFeed(new ReceivedBookmarkFeed(fcpMessage));
317 fcpListenerManager.fireMessageReceived(fcpMessage);
322 * Handles a disconnect from the node.
325 * The exception that caused the disconnect, or
326 * <code>null</code> if there was no exception
328 synchronized void handleDisconnect(Throwable throwable) {
329 FcpUtils.close(remoteInputStream);
330 FcpUtils.close(remoteOutputStream);
331 FcpUtils.close(remoteSocket);
332 if (connectionHandler != null) {
333 connectionHandler.stop();
334 connectionHandler = null;
335 fcpListenerManager.fireConnectionClosed(throwable);
344 * Incremets the counter in {@link #incomingMessageStatistics} by
345 * <cod>1</code> for the given message name.
348 * The name of the message to count
350 private void countMessage(String name) {
352 if (incomingMessageStatistics.containsKey(name)) {
353 oldValue = incomingMessageStatistics.get(name);
355 incomingMessageStatistics.put(name, oldValue + 1);
356 logger.finest("count for " + name + ": " + (oldValue + 1));
359 private synchronized InputStream getInputStream(long dataLength) throws IOException {
360 return new TempInputStream(remoteInputStream, dataLength);