add SimpleProgress
[jSite2.git] / src / net / pterodactylus / util / fcp / FcpConnection.java
1 /*
2  * jSite2 - FpcConnection.java -
3  * Copyright © 2008 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package net.pterodactylus.util.fcp;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.net.InetAddress;
26 import java.net.Socket;
27 import java.net.UnknownHostException;
28 import java.util.ArrayList;
29 import java.util.List;
30
31 import net.pterodactylus.util.io.Closer;
32 import net.pterodactylus.util.io.LimitedInputStream;
33
34 /**
35  * An FCP connection to a Freenet node.
36  * 
37  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
38  * @version $Id$
39  */
40 public class FcpConnection {
41
42         /** The default port for FCP v2. */
43         public static final int DEFAULT_PORT = 9481;
44
45         /** The list of FCP listeners. */
46         private final List<FcpListener> fcpListeners = new ArrayList<FcpListener>();
47
48         /** The address of the node. */
49         private final InetAddress address;
50
51         /** The port number of the node’s FCP port. */
52         private final int port;
53
54         /** The remote socket. */
55         private Socket remoteSocket;
56
57         /** The input stream from the node. */
58         private InputStream remoteInputStream;
59
60         /** The output stream to the node. */
61         private OutputStream remoteOutputStream;
62
63         /** The connection handler. */
64         private FcpConnectionHandler connectionHandler;
65
66         /**
67          * Creates a new FCP connection to the Freenet node running on the given
68          * host, listening on the default port.
69          * 
70          * @param host
71          *            The hostname of the Freenet node
72          * @throws UnknownHostException
73          *             if <code>host</code> can not be resolved
74          */
75         public FcpConnection(String host) throws UnknownHostException {
76                 this(host, DEFAULT_PORT);
77         }
78
79         /**
80          * Creates a new FCP connection to the Freenet node running on the given
81          * host, listening on the given port.
82          * 
83          * @param host
84          *            The hostname of the Freenet node
85          * @param port
86          *            The port number of the node’s FCP port
87          * @throws UnknownHostException
88          *             if <code>host</code> can not be resolved
89          */
90         public FcpConnection(String host, int port) throws UnknownHostException {
91                 this(InetAddress.getByName(host), port);
92         }
93
94         /**
95          * Creates a new FCP connection to the Freenet node running at the given
96          * address, listening on the default port.
97          * 
98          * @param address
99          *            The address of the Freenet node
100          */
101         public FcpConnection(InetAddress address) {
102                 this(address, DEFAULT_PORT);
103         }
104
105         /**
106          * Creates a new FCP connection to the Freenet node running at the given
107          * address, listening on the given port.
108          * 
109          * @param address
110          *            The address of the Freenet node
111          * @param port
112          *            The port number of the node’s FCP port
113          */
114         public FcpConnection(InetAddress address, int port) {
115                 this.address = address;
116                 this.port = port;
117         }
118
119         //
120         // LISTENER MANAGEMENT
121         //
122
123         /**
124          * Adds the given listener to the list of listeners.
125          * 
126          * @param fcpListener
127          *            The listener to add
128          */
129         public void addFcpListener(FcpListener fcpListener) {
130                 fcpListeners.add(fcpListener);
131         }
132
133         /**
134          * Removes the given listener from the list of listeners.
135          * 
136          * @param fcpListener
137          *            The listener to remove
138          */
139         public void removeFcpListener(FcpListener fcpListener) {
140                 fcpListeners.remove(fcpListener);
141         }
142
143         /**
144          * Notifies listeners that a “NodeHello” message was received.
145          * 
146          * @see FcpListener#receivedNodeHello(FcpConnection, NodeHello)
147          * @param nodeHello
148          *            The “NodeHello” message
149          */
150         private void fireReceivedNodeHello(NodeHello nodeHello) {
151                 for (FcpListener fcpListener: fcpListeners) {
152                         fcpListener.receivedNodeHello(this, nodeHello);
153                 }
154         }
155
156         /**
157          * Notifies listeners that a “CloseConnectionDuplicateClientName” message
158          * was received.
159          * 
160          * @see FcpListener#receivedCloseConnectionDuplicateClientName(FcpConnection,
161          *      CloseConnectionDuplicateClientName)
162          * @param closeConnectionDuplicateClientName
163          *            The “CloseConnectionDuplicateClientName” message
164          */
165         private void fireReceivedCloseConnectionDuplicateClientName(CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
166                 for (FcpListener fcpListener: fcpListeners) {
167                         fcpListener.receivedCloseConnectionDuplicateClientName(this, closeConnectionDuplicateClientName);
168                 }
169         }
170
171         /**
172          * Notifies listeners that a “SSKKeypair” message was received.
173          * 
174          * @see FcpListener#receivedSSKKeypair(FcpConnection, SSKKeypair)
175          * @param sskKeypair
176          *            The “SSKKeypair” message
177          */
178         private void fireReceivedSSKKeypair(SSKKeypair sskKeypair) {
179                 for (FcpListener fcpListener: fcpListeners) {
180                         fcpListener.receivedSSKKeypair(this, sskKeypair);
181                 }
182         }
183
184         /**
185          * Notifies listeners that a “Peer” message was received.
186          * 
187          * @see FcpListener#receivedPeer(FcpConnection, Peer)
188          * @param peer
189          *            The “Peer” message
190          */
191         private void fireReceivedPeer(Peer peer) {
192                 for (FcpListener fcpListener: fcpListeners) {
193                         fcpListener.receivedPeer(this, peer);
194                 }
195         }
196
197         /**
198          * Notifies all listeners that an “EndListPeers” message was received.
199          * 
200          * @see FcpListener#receivedEndListPeers(FcpConnection, EndListPeers)
201          * @param endListPeers
202          *            The “EndListPeers” message
203          */
204         private void fireReceivedEndListPeers(EndListPeers endListPeers) {
205                 for (FcpListener fcpListener: fcpListeners) {
206                         fcpListener.receivedEndListPeers(this, endListPeers);
207                 }
208         }
209
210         /**
211          * Notifies all listeners that a “PeerNote” message was received.
212          * 
213          * @see FcpListener#receivedPeerNote(FcpConnection, PeerNote)
214          * @param peerNote
215          */
216         private void fireReceivedPeerNote(PeerNote peerNote) {
217                 for (FcpListener fcpListener: fcpListeners) {
218                         fcpListener.receivedPeerNote(this, peerNote);
219                 }
220         }
221
222         /**
223          * Notifies all listeners that an “EndListPeerNotes” message was received.
224          * 
225          * @see FcpListener#receivedEndListPeerNotes(FcpConnection,
226          *      EndListPeerNotes)
227          * @param endListPeerNotes
228          *            The “EndListPeerNotes” message
229          */
230         private void fireReceivedEndListPeerNotes(EndListPeerNotes endListPeerNotes) {
231                 for (FcpListener fcpListener: fcpListeners) {
232                         fcpListener.receivedEndListPeerNotes(this, endListPeerNotes);
233                 }
234         }
235
236         /**
237          * Notifies all listeners that a “PeerRemoved” message was received.
238          * 
239          * @see FcpListener#receivedPeerRemoved(FcpConnection, PeerRemoved)
240          * @param peerRemoved
241          *            The “PeerRemoved” message
242          */
243         private void fireReceivedPeerRemoved(PeerRemoved peerRemoved) {
244                 for (FcpListener fcpListener: fcpListeners) {
245                         fcpListener.receivedPeerRemoved(this, peerRemoved);
246                 }
247         }
248
249         /**
250          * Notifies all listeners that a “NodeData” message was received.
251          * 
252          * @see FcpListener#receivedNodeData(FcpConnection, NodeData)
253          * @param nodeData
254          *            The “NodeData” message
255          */
256         private void fireReceivedNodeData(NodeData nodeData) {
257                 for (FcpListener fcpListener: fcpListeners) {
258                         fcpListener.receivedNodeData(this, nodeData);
259                 }
260         }
261
262         /**
263          * Notifies all listeners that a “TestDDAReply” message was received.
264          * 
265          * @see FcpListener#receivedTestDDAReply(FcpConnection, TestDDAReply)
266          * @param testDDAReply
267          *            The “TestDDAReply” message
268          */
269         private void fireReceivedTestDDAReply(TestDDAReply testDDAReply) {
270                 for (FcpListener fcpListener: fcpListeners) {
271                         fcpListener.receivedTestDDAReply(this, testDDAReply);
272                 }
273         }
274
275         /**
276          * Notifies all listeners that a “TestDDAComplete” message was received.
277          * 
278          * @see FcpListener#receivedTestDDAComplete(FcpConnection, TestDDAComplete)
279          * @param testDDAComplete
280          *            The “TestDDAComplete” message
281          */
282         private void fireReceivedTestDDAComplete(TestDDAComplete testDDAComplete) {
283                 for (FcpListener fcpListener: fcpListeners) {
284                         fcpListener.receivedTestDDAComplete(this, testDDAComplete);
285                 }
286         }
287
288         /**
289          * Notifies all listeners that a “PersistentPut” message was received.
290          * 
291          * @see FcpListener#receivedPersistentPut(FcpConnection, PersistentPut)
292          * @param persistentPut
293          *            The “PersistentPut” message
294          */
295         private void fireReceivedPersistentPut(PersistentPut persistentPut) {
296                 for (FcpListener fcpListener: fcpListeners) {
297                         fcpListener.receivedPersistentPut(this, persistentPut);
298                 }
299         }
300
301         /**
302          * Notifies all listeners that a “EndListPersistentRequests” message was
303          * received.
304          * 
305          * @param endListPersistentRequests
306          *            The “EndListPersistentRequests” message
307          */
308         private void fireReceivedEndListPersistentRequests(EndListPersistentRequests endListPersistentRequests) {
309                 for (FcpListener fcpListener: fcpListeners) {
310                         fcpListener.receivedEndListPersistentRequests(this, endListPersistentRequests);
311                 }
312         }
313
314         /**
315          * Notifies all listeners that a “URIGenerated” message was received.
316          * 
317          * @param uriGenerated
318          *            The “URIGenerated” message
319          */
320         private void fireReceivedURIGenerated(URIGenerated uriGenerated) {
321                 for (FcpListener fcpListener: fcpListeners) {
322                         fcpListener.receivedURIGenerated(this, uriGenerated);
323                 }
324         }
325
326         /**
327          * Notifies all listeners that an “AllData” message was received.
328          * 
329          * @param allData
330          *            The “AllData” message
331          */
332         private void fireReceivedAllData(AllData allData) {
333                 for (FcpListener fcpListener: fcpListeners) {
334                         fcpListener.receivedAllData(this, allData);
335                 }
336         }
337
338         /**
339          * Notifies all listeners that a “SimpleProgress” message was received.
340          * 
341          * @param simpleProgress
342          *            The “SimpleProgress” message
343          */
344         private void fireReceivedSimpleProgress(SimpleProgress simpleProgress) {
345                 for (FcpListener fcpListener: fcpListeners) {
346                         fcpListener.receivedSimpleProgress(this, simpleProgress);
347                 }
348         }
349
350         /**
351          * Notifies all listeners that a “ProtocolError” message was received.
352          * 
353          * @param protocolError
354          *            The “ProtocolError” message
355          */
356         private void fireReceivedProtocolError(ProtocolError protocolError) {
357                 for (FcpListener fcpListener: fcpListeners) {
358                         fcpListener.receivedProtocolError(this, protocolError);
359                 }
360         }
361
362         /**
363          * Notifies all registered listeners that a message has been received.
364          * 
365          * @see FcpListener#receivedMessage(FcpConnection, FcpMessage)
366          * @param fcpMessage
367          *            The message that was received
368          */
369         private void fireMessageReceived(FcpMessage fcpMessage) {
370                 for (FcpListener fcpListener: fcpListeners) {
371                         fcpListener.receivedMessage(this, fcpMessage);
372                 }
373         }
374
375         //
376         // ACTIONS
377         //
378
379         /**
380          * Connects to the node.
381          * 
382          * @throws IOException
383          *             if an I/O error occurs
384          * @throws IllegalStateException
385          *             if there is already a connection to the node
386          */
387         public synchronized void connect() throws IOException, IllegalStateException {
388                 if (connectionHandler != null) {
389                         throw new IllegalStateException("already connected, disconnect first");
390                 }
391                 remoteSocket = new Socket(address, port);
392                 remoteInputStream = remoteSocket.getInputStream();
393                 remoteOutputStream = remoteSocket.getOutputStream();
394                 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
395         }
396
397         /**
398          * Disconnects from the node. If there is no connection to the node, this
399          * method does nothing.
400          */
401         public synchronized void disconnect() {
402                 if (connectionHandler == null) {
403                         return;
404                 }
405                 Closer.close(remoteSocket);
406                 connectionHandler.stop();
407                 connectionHandler = null;
408         }
409
410         /**
411          * Sends the given FCP message.
412          * 
413          * @param fcpMessage
414          *            The FCP message to send
415          * @throws IOException
416          *             if an I/O error occurs
417          */
418         public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
419                 System.out.println("sending message: " + fcpMessage.getName());
420                 fcpMessage.write(remoteOutputStream);
421         }
422
423         //
424         // PACKAGE-PRIVATE METHODS
425         //
426
427         /**
428          * Handles the given message, notifying listeners. This message should only
429          * be called by {@link FcpConnectionHandler}.
430          * 
431          * @param fcpMessage
432          *            The received message
433          */
434         void handleMessage(FcpMessage fcpMessage) {
435                 String messageName = fcpMessage.getName();
436                 if ("SimpleProgress".equals(messageName)) {
437                         fireReceivedSimpleProgress(new SimpleProgress(fcpMessage));
438                 } else if ("ProtocolError".equals(messageName)) {
439                         fireReceivedProtocolError(new ProtocolError(fcpMessage));
440                 } else if ("PersistentPut".equals(messageName)) {
441                         fireReceivedPersistentPut(new PersistentPut(fcpMessage));
442                 } else if ("URIGenerated".equals(messageName)) {
443                         fireReceivedURIGenerated(new URIGenerated(fcpMessage));
444                 } else if ("EndListPersistentRequests".equals(messageName)) {
445                         fireReceivedEndListPersistentRequests(new EndListPersistentRequests(fcpMessage));
446                 } else if ("Peer".equals(messageName)) {
447                         fireReceivedPeer(new Peer(fcpMessage));
448                 } else if ("PeerNote".equals(messageName)) {
449                         fireReceivedPeerNote(new PeerNote(fcpMessage));
450                 } else if ("AllData".equals(messageName)) {
451                         long dataLength;
452                         try {
453                                 dataLength = Long.valueOf(fcpMessage.getField("DataLength"));
454                         } catch (NumberFormatException nfe1) {
455                                 dataLength = -1;
456                         }
457                         LimitedInputStream payloadInputStream = new LimitedInputStream(remoteInputStream, dataLength);
458                         fireReceivedAllData(new AllData(fcpMessage, payloadInputStream));
459                         try {
460                                 payloadInputStream.consume();
461                         } catch (IOException ioe1) {
462                                 /* FIXME - what now? */
463                                 /* well, ignore. when the connection handler fails, all fails. */
464                         }
465                 } else if ("EndListPeerNotes".equals(messageName)) {
466                         fireReceivedEndListPeerNotes(new EndListPeerNotes(fcpMessage));
467                 } else if ("EndListPeers".equals(messageName)) {
468                         fireReceivedEndListPeers(new EndListPeers(fcpMessage));
469                 } else if ("SSKKeypair".equals(messageName)) {
470                         fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
471                 } else if ("PeerRemoved".equals(messageName)) {
472                         fireReceivedPeerRemoved(new PeerRemoved(fcpMessage));
473                 } else if ("NodeData".equals(messageName)) {
474                         fireReceivedNodeData(new NodeData(fcpMessage));
475                 } else if ("TestDDAReply".equals(messageName)) {
476                         fireReceivedTestDDAReply(new TestDDAReply(fcpMessage));
477                 } else if ("TestDDAComplete".equals(messageName)) {
478                         fireReceivedTestDDAComplete(new TestDDAComplete(fcpMessage));
479                 } else if ("NodeHello".equals(messageName)) {
480                         fireReceivedNodeHello(new NodeHello(fcpMessage));
481                 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
482                         fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
483                 } else {
484                         fireMessageReceived(fcpMessage);
485                 }
486         }
487
488 }