c1591578ec5e8960225f35a96d40269bd52b903d
[jFCPlib.git] / src / main / java / net / pterodactylus / fcp / highlevel / FcpClient.java
1 /*
2  * jFCPlib - FcpClient.java - Copyright © 2009 David Roden
3  *
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.
8  *
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.
13  *
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.
17  */
18
19 package net.pterodactylus.fcp.highlevel;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.net.InetAddress;
24 import java.net.URL;
25 import java.net.UnknownHostException;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.Map.Entry;
33 import java.util.concurrent.CountDownLatch;
34
35 import net.pterodactylus.fcp.AddPeer;
36 import net.pterodactylus.fcp.ClientHello;
37 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
38 import net.pterodactylus.fcp.DataFound;
39 import net.pterodactylus.fcp.EndListPeerNotes;
40 import net.pterodactylus.fcp.EndListPeers;
41 import net.pterodactylus.fcp.EndListPersistentRequests;
42 import net.pterodactylus.fcp.FCPPluginMessage;
43 import net.pterodactylus.fcp.FCPPluginReply;
44 import net.pterodactylus.fcp.FcpAdapter;
45 import net.pterodactylus.fcp.FcpConnection;
46 import net.pterodactylus.fcp.FcpListener;
47 import net.pterodactylus.fcp.GenerateSSK;
48 import net.pterodactylus.fcp.GetFailed;
49 import net.pterodactylus.fcp.GetNode;
50 import net.pterodactylus.fcp.ListPeerNotes;
51 import net.pterodactylus.fcp.ListPeers;
52 import net.pterodactylus.fcp.ListPersistentRequests;
53 import net.pterodactylus.fcp.ModifyPeer;
54 import net.pterodactylus.fcp.ModifyPeerNote;
55 import net.pterodactylus.fcp.NodeData;
56 import net.pterodactylus.fcp.NodeHello;
57 import net.pterodactylus.fcp.NodeRef;
58 import net.pterodactylus.fcp.Peer;
59 import net.pterodactylus.fcp.PeerNote;
60 import net.pterodactylus.fcp.PeerRemoved;
61 import net.pterodactylus.fcp.PersistentGet;
62 import net.pterodactylus.fcp.PersistentPut;
63 import net.pterodactylus.fcp.ProtocolError;
64 import net.pterodactylus.fcp.RemovePeer;
65 import net.pterodactylus.fcp.SSKKeypair;
66 import net.pterodactylus.fcp.SimpleProgress;
67 import net.pterodactylus.fcp.WatchGlobal;
68 import net.pterodactylus.util.filter.Filter;
69 import net.pterodactylus.util.filter.Filters;
70 import net.pterodactylus.util.thread.ObjectWrapper;
71
72 /**
73  * High-level FCP client that hides the details of the underlying FCP
74  * implementation.
75  *
76  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
77  */
78 public class FcpClient {
79
80         /** Object used for synchronization. */
81         private final Object syncObject = new Object();
82
83         /** Listener management. */
84         private final FcpClientListenerManager fcpClientListenerManager = new FcpClientListenerManager(this);
85
86         /** The name of this client. */
87         private final String name;
88
89         /** The underlying FCP connection. */
90         private final FcpConnection fcpConnection;
91
92         /** The {@link NodeHello} data sent by the node on connection. */
93         private volatile NodeHello nodeHello;
94
95         /** Whether the client is currently connected. */
96         private volatile boolean connected;
97
98         /**
99          * Creates an FCP client with the given name.
100          *
101          * @param name
102          *            The name of the FCP client
103          * @throws UnknownHostException
104          *             if the hostname “localhost” is unknown
105          */
106         public FcpClient(String name) throws UnknownHostException {
107                 this(name, "localhost");
108         }
109
110         /**
111          * Creates an FCP client.
112          *
113          * @param name
114          *            The name of the FCP client
115          * @param hostname
116          *            The hostname of the Freenet node
117          * @throws UnknownHostException
118          *             if the given hostname can not be resolved
119          */
120         public FcpClient(String name, String hostname) throws UnknownHostException {
121                 this(name, hostname, FcpConnection.DEFAULT_PORT);
122         }
123
124         /**
125          * Creates an FCP client.
126          *
127          * @param name
128          *            The name of the FCP client
129          * @param hostname
130          *            The hostname of the Freenet node
131          * @param port
132          *            The Freenet node’s FCP port
133          * @throws UnknownHostException
134          *             if the given hostname can not be resolved
135          */
136         public FcpClient(String name, String hostname, int port) throws UnknownHostException {
137                 this(name, InetAddress.getByName(hostname), port);
138         }
139
140         /**
141          * Creates an FCP client.
142          *
143          * @param name
144          *            The name of the FCP client
145          * @param host
146          *            The host address of the Freenet node
147          */
148         public FcpClient(String name, InetAddress host) {
149                 this(name, host, FcpConnection.DEFAULT_PORT);
150         }
151
152         /**
153          * Creates an FCP client.
154          *
155          * @param name
156          *            The name of the FCP client
157          * @param host
158          *            The host address of the Freenet node
159          * @param port
160          *            The Freenet node’s FCP port
161          */
162         public FcpClient(String name, InetAddress host, int port) {
163                 this.name = name;
164                 fcpConnection = new FcpConnection(host, port);
165                 fcpConnection.addFcpListener(new FcpAdapter() {
166
167                         /**
168                          * {@inheritDoc}
169                          */
170                         @Override
171                         @SuppressWarnings("synthetic-access")
172                         public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
173                                 connected = false;
174                                 fcpClientListenerManager.fireFcpClientDisconnected();
175                         }
176                 });
177         }
178
179         //
180         // LISTENER MANAGEMENT
181         //
182
183         /**
184          * Adds an FCP listener to the underlying connection.
185          *
186          * @param fcpListener
187          *            The FCP listener to add
188          */
189         public void addFcpListener(FcpListener fcpListener) {
190                 fcpConnection.addFcpListener(fcpListener);
191         }
192
193         /**
194          * Removes an FCP listener from the underlying connection.
195          *
196          * @param fcpListener
197          *            The FCP listener to remove
198          */
199         public void removeFcpListener(FcpListener fcpListener) {
200                 fcpConnection.removeFcpListener(fcpListener);
201         }
202
203         /**
204          * Adds an FCP client listener to the list of registered listeners.
205          *
206          * @param fcpClientListener
207          *            The FCP client listener to add
208          */
209         public void addFcpClientListener(FcpClientListener fcpClientListener) {
210                 fcpClientListenerManager.addListener(fcpClientListener);
211         }
212
213         /**
214          * Removes an FCP client listener from the list of registered listeners.
215          *
216          * @param fcpClientListener
217          *            The FCP client listener to remove
218          */
219         public void removeFcpClientListener(FcpClientListener fcpClientListener) {
220                 fcpClientListenerManager.removeListener(fcpClientListener);
221         }
222
223         //
224         // ACCESSORS
225         //
226
227         /**
228          * Returns the {@link NodeHello} object that the node returned when
229          * connecting.
230          *
231          * @return The {@code NodeHello} data container
232          */
233         public NodeHello getNodeHello() {
234                 return nodeHello;
235         }
236
237         /**
238          * Returns the underlying FCP connection.
239          *
240          * @return The underlying FCP connection
241          */
242         public FcpConnection getConnection() {
243                 return fcpConnection;
244         }
245
246         //
247         // ACTIONS
248         //
249
250         /**
251          * Connects the FCP client.
252          *
253          * @throws IOException
254          *             if an I/O error occurs
255          * @throws FcpException
256          *             if an FCP error occurs
257          */
258         public void connect() throws IOException, FcpException {
259                 checkConnected(false);
260                 connected = true;
261                 new ExtendedFcpAdapter() {
262
263                         /**
264                          * {@inheritDoc}
265                          */
266                         @Override
267                         @SuppressWarnings("synthetic-access")
268                         public void run() throws IOException {
269                                 fcpConnection.connect();
270                                 ClientHello clientHello = new ClientHello(name);
271                                 fcpConnection.sendMessage(clientHello);
272                                 WatchGlobal watchGlobal = new WatchGlobal(true);
273                                 fcpConnection.sendMessage(watchGlobal);
274                         }
275
276                         /**
277                          * {@inheritDoc}
278                          */
279                         @Override
280                         @SuppressWarnings("synthetic-access")
281                         public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
282                                 FcpClient.this.nodeHello = nodeHello;
283                                 completionLatch.countDown();
284                         }
285                 }.execute();
286         }
287
288         /**
289          * Disconnects the FCP client.
290          */
291         public void disconnect() {
292                 synchronized (syncObject) {
293                         fcpConnection.close();
294                         syncObject.notifyAll();
295                 }
296         }
297
298         /**
299          * Returns whether this client is currently connected.
300          *
301          * @return {@code true} if the client is currently connected, {@code false}
302          *         otherwise
303          */
304         public boolean isConnected() {
305                 return connected;
306         }
307
308         //
309         // PEER MANAGEMENT
310         //
311
312         /**
313          * Returns all peers that the node has.
314          *
315          * @param withMetadata
316          *            <code>true</code> to include peer metadata
317          * @param withVolatile
318          *            <code>true</code> to include volatile peer data
319          * @return A set containing the node’s peers
320          * @throws IOException
321          *             if an I/O error occurs
322          * @throws FcpException
323          *             if an FCP error occurs
324          */
325         public Collection<Peer> getPeers(final boolean withMetadata, final boolean withVolatile) throws IOException, FcpException {
326                 final Set<Peer> peers = Collections.synchronizedSet(new HashSet<Peer>());
327                 new ExtendedFcpAdapter() {
328
329                         /** The ID of the “ListPeers” request. */
330                         @SuppressWarnings("synthetic-access")
331                         private String identifier = createIdentifier("list-peers");
332
333                         /**
334                          * {@inheritDoc}
335                          */
336                         @Override
337                         @SuppressWarnings("synthetic-access")
338                         public void run() throws IOException {
339                                 fcpConnection.sendMessage(new ListPeers(identifier, withMetadata, withVolatile));
340                         }
341
342                         /**
343                          * {@inheritDoc}
344                          */
345                         @Override
346                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
347                                 if (peer.getIdentifier().equals(identifier)) {
348                                         peers.add(peer);
349                                 }
350                         }
351
352                         /**
353                          * {@inheritDoc}
354                          */
355                         @Override
356                         public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
357                                 if (endListPeers.getIdentifier().equals(identifier)) {
358                                         completionLatch.countDown();
359                                 }
360                         }
361                 }.execute();
362                 return peers;
363         }
364
365         /**
366          * Returns all darknet peers.
367          *
368          * @param withMetadata
369          *            <code>true</code> to include peer metadata
370          * @param withVolatile
371          *            <code>true</code> to include volatile peer data
372          * @return A set containing the node’s darknet peers
373          * @throws IOException
374          *             if an I/O error occurs
375          * @throws FcpException
376          *             if an FCP error occurs
377          */
378         public Collection<Peer> getDarknetPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
379                 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
380                 Collection<Peer> darknetPeers = new HashSet<Peer>();
381                 for (Peer peer : allPeers) {
382                         if (!peer.isOpennet() && !peer.isSeed()) {
383                                 darknetPeers.add(peer);
384                         }
385                 }
386                 return darknetPeers;
387         }
388
389         /**
390          * Returns all opennet peers.
391          *
392          * @param withMetadata
393          *            <code>true</code> to include peer metadata
394          * @param withVolatile
395          *            <code>true</code> to include volatile peer data
396          * @return A set containing the node’s opennet peers
397          * @throws IOException
398          *             if an I/O error occurs
399          * @throws FcpException
400          *             if an FCP error occurs
401          */
402         public Collection<Peer> getOpennetPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
403                 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
404                 Collection<Peer> opennetPeers = new HashSet<Peer>();
405                 for (Peer peer : allPeers) {
406                         if (peer.isOpennet() && !peer.isSeed()) {
407                                 opennetPeers.add(peer);
408                         }
409                 }
410                 return opennetPeers;
411         }
412
413         /**
414          * Returns all seed peers.
415          *
416          * @param withMetadata
417          *            <code>true</code> to include peer metadata
418          * @param withVolatile
419          *            <code>true</code> to include volatile peer data
420          * @return A set containing the node’s seed peers
421          * @throws IOException
422          *             if an I/O error occurs
423          * @throws FcpException
424          *             if an FCP error occurs
425          */
426         public Collection<Peer> getSeedPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
427                 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
428                 Collection<Peer> seedPeers = new HashSet<Peer>();
429                 for (Peer peer : allPeers) {
430                         if (peer.isSeed()) {
431                                 seedPeers.add(peer);
432                         }
433                 }
434                 return seedPeers;
435         }
436
437         /**
438          * Adds the given peer to the node.
439          *
440          * @param peer
441          *            The peer to add
442          * @throws IOException
443          *             if an I/O error occurs
444          * @throws FcpException
445          *             if an FCP error occurs
446          */
447         public void addPeer(Peer peer) throws IOException, FcpException {
448                 addPeer(peer.getNodeRef());
449         }
450
451         /**
452          * Adds the peer defined by the noderef to the node.
453          *
454          * @param nodeRef
455          *            The noderef that defines the new peer
456          * @throws IOException
457          *             if an I/O error occurs
458          * @throws FcpException
459          *             if an FCP error occurs
460          */
461         public void addPeer(NodeRef nodeRef) throws IOException, FcpException {
462                 addPeer(new AddPeer(nodeRef));
463         }
464
465         /**
466          * Adds a peer, reading the noderef from the given URL.
467          *
468          * @param url
469          *            The URL to read the noderef from
470          * @throws IOException
471          *             if an I/O error occurs
472          * @throws FcpException
473          *             if an FCP error occurs
474          */
475         public void addPeer(URL url) throws IOException, FcpException {
476                 addPeer(new AddPeer(url));
477         }
478
479         /**
480          * Adds a peer, reading the noderef of the peer from the given file.
481          * <strong>Note:</strong> the file to read the noderef from has to reside on
482          * the same machine as the node!
483          *
484          * @param file
485          *            The name of the file containing the peer’s noderef
486          * @throws IOException
487          *             if an I/O error occurs
488          * @throws FcpException
489          *             if an FCP error occurs
490          */
491         public void addPeer(String file) throws IOException, FcpException {
492                 addPeer(new AddPeer(file));
493         }
494
495         /**
496          * Sends the given {@link AddPeer} message to the node. This method should
497          * not be called directly. Use one of {@link #addPeer(Peer)},
498          * {@link #addPeer(NodeRef)}, {@link #addPeer(URL)}, or
499          * {@link #addPeer(String)} instead.
500          *
501          * @param addPeer
502          *            The “AddPeer” message
503          * @throws IOException
504          *             if an I/O error occurs
505          * @throws FcpException
506          *             if an FCP error occurs
507          */
508         private void addPeer(final AddPeer addPeer) throws IOException, FcpException {
509                 new ExtendedFcpAdapter() {
510
511                         /**
512                          * {@inheritDoc}
513                          */
514                         @Override
515                         @SuppressWarnings("synthetic-access")
516                         public void run() throws IOException {
517                                 fcpConnection.sendMessage(addPeer);
518                         }
519
520                         /**
521                          * {@inheritDoc}
522                          */
523                         @Override
524                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
525                                 completionLatch.countDown();
526                         }
527                 }.execute();
528         }
529
530         /**
531          * Modifies the given peer.
532          *
533          * @param peer
534          *            The peer to modify
535          * @param allowLocalAddresses
536          *            <code>true</code> to allow local address, <code>false</code>
537          *            to not allow local address, <code>null</code> to not change
538          *            the setting
539          * @param disabled
540          *            <code>true</code> to disable the peer, <code>false</code> to
541          *            enable the peer, <code>null</code> to not change the setting
542          * @param listenOnly
543          *            <code>true</code> to enable “listen only” for the peer,
544          *            <code>false</code> to disable it, <code>null</code> to not
545          *            change it
546          * @throws IOException
547          *             if an I/O error occurs
548          * @throws FcpException
549          *             if an FCP error occurs
550          */
551         public void modifyPeer(final Peer peer, final Boolean allowLocalAddresses, final Boolean disabled, final Boolean listenOnly) throws IOException, FcpException {
552                 new ExtendedFcpAdapter() {
553
554                         /**
555                          * {@inheritDoc}
556                          */
557                         @Override
558                         @SuppressWarnings("synthetic-access")
559                         public void run() throws IOException {
560                                 fcpConnection.sendMessage(new ModifyPeer(peer.getIdentity(), allowLocalAddresses, disabled, listenOnly));
561                         }
562
563                         /**
564                          * {@inheritDoc}
565                          */
566                         @Override
567                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
568                                 completionLatch.countDown();
569                         }
570                 }.execute();
571         }
572
573         /**
574          * Removes the given peer.
575          *
576          * @param peer
577          *            The peer to remove
578          * @throws IOException
579          *             if an I/O error occurs
580          * @throws FcpException
581          *             if an FCP error occurs
582          */
583         public void removePeer(final Peer peer) throws IOException, FcpException {
584                 new ExtendedFcpAdapter() {
585
586                         /**
587                          * {@inheritDoc}
588                          */
589                         @Override
590                         @SuppressWarnings("synthetic-access")
591                         public void run() throws IOException {
592                                 fcpConnection.sendMessage(new RemovePeer(peer.getIdentity()));
593                         }
594
595                         /**
596                          * {@inheritDoc}
597                          */
598                         @Override
599                         public void receivedPeerRemoved(FcpConnection fcpConnection, PeerRemoved peerRemoved) {
600                                 completionLatch.countDown();
601                         }
602                 }.execute();
603         }
604
605         //
606         // PEER NOTES MANAGEMENT
607         //
608
609         /**
610          * Returns the peer note of the given peer.
611          *
612          * @param peer
613          *            The peer to get the note for
614          * @return The peer’s note
615          * @throws IOException
616          *             if an I/O error occurs
617          * @throws FcpException
618          *             if an FCP error occurs
619          */
620         public PeerNote getPeerNote(final Peer peer) throws IOException, FcpException {
621                 final ObjectWrapper<PeerNote> objectWrapper = new ObjectWrapper<PeerNote>();
622                 new ExtendedFcpAdapter() {
623
624                         /**
625                          * {@inheritDoc}
626                          */
627                         @Override
628                         @SuppressWarnings("synthetic-access")
629                         public void run() throws IOException {
630                                 fcpConnection.sendMessage(new ListPeerNotes(peer.getIdentity()));
631                         }
632
633                         /**
634                          * {@inheritDoc}
635                          */
636                         @Override
637                         public void receivedPeerNote(FcpConnection fcpConnection, PeerNote peerNote) {
638                                 if (peerNote.getNodeIdentifier().equals(peer.getIdentity())) {
639                                         objectWrapper.set(peerNote);
640                                 }
641                         }
642
643                         /**
644                          * {@inheritDoc}
645                          */
646                         @Override
647                         public void receivedEndListPeerNotes(FcpConnection fcpConnection, EndListPeerNotes endListPeerNotes) {
648                                 completionLatch.countDown();
649                         }
650                 }.execute();
651                 return objectWrapper.get();
652         }
653
654         /**
655          * Replaces the peer note for the given peer.
656          *
657          * @param peer
658          *            The peer
659          * @param noteText
660          *            The new base64-encoded note text
661          * @param noteType
662          *            The type of the note (currently only <code>1</code> is
663          *            allowed)
664          * @throws IOException
665          *             if an I/O error occurs
666          * @throws FcpException
667          *             if an FCP error occurs
668          */
669         public void modifyPeerNote(final Peer peer, final String noteText, final int noteType) throws IOException, FcpException {
670                 new ExtendedFcpAdapter() {
671
672                         /**
673                          * {@inheritDoc}
674                          */
675                         @Override
676                         @SuppressWarnings("synthetic-access")
677                         public void run() throws IOException {
678                                 fcpConnection.sendMessage(new ModifyPeerNote(peer.getIdentity(), noteText, noteType));
679                         }
680
681                         /**
682                          * {@inheritDoc}
683                          */
684                         @Override
685                         public void receivedPeer(FcpConnection fcpConnection, Peer receivedPeer) {
686                                 if (receivedPeer.getIdentity().equals(peer.getIdentity())) {
687                                         completionLatch.countDown();
688                                 }
689                         }
690                 }.execute();
691         }
692
693         //
694         // KEY GENERATION
695         //
696
697         /**
698          * Generates a new SSK key pair.
699          *
700          * @return The generated key pair
701          * @throws IOException
702          *             if an I/O error occurs
703          * @throws FcpException
704          *             if an FCP error occurs
705          */
706         public SSKKeypair generateKeyPair() throws IOException, FcpException {
707                 final ObjectWrapper<SSKKeypair> sskKeypairWrapper = new ObjectWrapper<SSKKeypair>();
708                 new ExtendedFcpAdapter() {
709
710                         /**
711                          * {@inheritDoc}
712                          */
713                         @Override
714                         @SuppressWarnings("synthetic-access")
715                         public void run() throws IOException {
716                                 fcpConnection.sendMessage(new GenerateSSK());
717                         }
718
719                         /**
720                          * {@inheritDoc}
721                          */
722                         @Override
723                         public void receivedSSKKeypair(FcpConnection fcpConnection, SSKKeypair sskKeypair) {
724                                 sskKeypairWrapper.set(sskKeypair);
725                                 completionLatch.countDown();
726                         }
727                 }.execute();
728                 return sskKeypairWrapper.get();
729         }
730
731         //
732         // REQUEST MANAGEMENT
733         //
734
735         /**
736          * Returns all currently visible persistent get requests.
737          *
738          * @param global
739          *            <code>true</code> to return get requests from the global
740          *            queue, <code>false</code> to only show requests from the
741          *            client-local queue
742          * @return All get requests
743          * @throws IOException
744          *             if an I/O error occurs
745          * @throws FcpException
746          *             if an FCP error occurs
747          */
748         public Collection<Request> getGetRequests(final boolean global) throws IOException, FcpException {
749                 return Filters.filteredCollection(getRequests(global), new Filter<Request>() {
750
751                         /**
752                          * {@inheritDoc}
753                          */
754                         public boolean filterObject(Request request) {
755                                 return request instanceof GetRequest;
756                         }
757                 });
758         }
759
760         /**
761          * Returns all currently visible persistent put requests.
762          *
763          * @param global
764          *            <code>true</code> to return put requests from the global
765          *            queue, <code>false</code> to only show requests from the
766          *            client-local queue
767          * @return All put requests
768          * @throws IOException
769          *             if an I/O error occurs
770          * @throws FcpException
771          *             if an FCP error occurs
772          */
773         public Collection<Request> getPutRequests(final boolean global) throws IOException, FcpException {
774                 return Filters.filteredCollection(getRequests(global), new Filter<Request>() {
775
776                         /**
777                          * {@inheritDoc}
778                          */
779                         public boolean filterObject(Request request) {
780                                 return request instanceof PutRequest;
781                         }
782                 });
783         }
784
785         /**
786          * Returns all currently visible persistent requests.
787          *
788          * @param global
789          *            <code>true</code> to return requests from the global queue,
790          *            <code>false</code> to only show requests from the client-local
791          *            queue
792          * @return All requests
793          * @throws IOException
794          *             if an I/O error occurs
795          * @throws FcpException
796          *             if an FCP error occurs
797          */
798         public Collection<Request> getRequests(final boolean global) throws IOException, FcpException {
799                 final Map<String, Request> requests = Collections.synchronizedMap(new HashMap<String, Request>());
800                 new ExtendedFcpAdapter() {
801
802                         /**
803                          * {@inheritDoc}
804                          */
805                         @Override
806                         @SuppressWarnings("synthetic-access")
807                         public void run() throws IOException {
808                                 fcpConnection.sendMessage(new ListPersistentRequests());
809                         }
810
811                         /**
812                          * {@inheritDoc}
813                          */
814                         @Override
815                         public void receivedPersistentGet(FcpConnection fcpConnection, PersistentGet persistentGet) {
816                                 if (!persistentGet.isGlobal() || global) {
817                                         GetRequest getRequest = new GetRequest(persistentGet);
818                                         requests.put(persistentGet.getIdentifier(), getRequest);
819                                 }
820                         }
821
822                         /**
823                          * {@inheritDoc}
824                          *
825                          * @see net.pterodactylus.fcp.FcpAdapter#receivedDataFound(net.pterodactylus.fcp.FcpConnection,
826                          *      net.pterodactylus.fcp.DataFound)
827                          */
828                         @Override
829                         public void receivedDataFound(FcpConnection fcpConnection, DataFound dataFound) {
830                                 Request getRequest = requests.get(dataFound.getIdentifier());
831                                 if (getRequest == null) {
832                                         return;
833                                 }
834                                 getRequest.setComplete(true);
835                                 getRequest.setLength(dataFound.getDataLength());
836                                 getRequest.setContentType(dataFound.getMetadataContentType());
837                         }
838
839                         /**
840                          * {@inheritDoc}
841                          *
842                          * @see net.pterodactylus.fcp.FcpAdapter#receivedGetFailed(net.pterodactylus.fcp.FcpConnection,
843                          *      net.pterodactylus.fcp.GetFailed)
844                          */
845                         @Override
846                         public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
847                                 Request getRequest = requests.get(getFailed.getIdentifier());
848                                 if (getRequest == null) {
849                                         return;
850                                 }
851                                 getRequest.setComplete(true);
852                                 getRequest.setFailed(true);
853                                 getRequest.setFatal(getFailed.isFatal());
854                                 getRequest.setErrorCode(getFailed.getCode());
855                         }
856
857                         /**
858                          * {@inheritDoc}
859                          *
860                          * @see net.pterodactylus.fcp.FcpAdapter#receivedPersistentPut(net.pterodactylus.fcp.FcpConnection,
861                          *      net.pterodactylus.fcp.PersistentPut)
862                          */
863                         @Override
864                         public void receivedPersistentPut(FcpConnection fcpConnection, PersistentPut persistentPut) {
865                                 if (!persistentPut.isGlobal() || global) {
866                                         PutRequest putRequest = new PutRequest(persistentPut);
867                                         requests.put(persistentPut.getIdentifier(), putRequest);
868                                 }
869                         }
870
871                         /**
872                          * {@inheritDoc}
873                          *
874                          * @see net.pterodactylus.fcp.FcpAdapter#receivedSimpleProgress(net.pterodactylus.fcp.FcpConnection,
875                          *      net.pterodactylus.fcp.SimpleProgress)
876                          */
877                         @Override
878                         public void receivedSimpleProgress(FcpConnection fcpConnection, SimpleProgress simpleProgress) {
879                                 Request request = requests.get(simpleProgress.getIdentifier());
880                                 if (request == null) {
881                                         return;
882                                 }
883                                 request.setTotalBlocks(simpleProgress.getTotal());
884                                 request.setRequiredBlocks(simpleProgress.getRequired());
885                                 request.setFailedBlocks(simpleProgress.getFailed());
886                                 request.setFatallyFailedBlocks(simpleProgress.getFatallyFailed());
887                                 request.setSucceededBlocks(simpleProgress.getSucceeded());
888                                 request.setFinalizedTotal(simpleProgress.isFinalizedTotal());
889                         }
890
891                         /**
892                          * {@inheritDoc}
893                          */
894                         @Override
895                         public void receivedEndListPersistentRequests(FcpConnection fcpConnection, EndListPersistentRequests endListPersistentRequests) {
896                                 completionLatch.countDown();
897                         }
898                 }.execute();
899                 return requests.values();
900         }
901
902         /**
903          * Sends a message to a plugin and waits for the response.
904          *
905          * @param pluginClass
906          *            The name of the plugin class
907          * @param parameters
908          *            The parameters for the plugin
909          * @return The responses from the plugin
910          * @throws FcpException
911          *             if an FCP error occurs
912          * @throws IOException
913          *             if an I/O error occurs
914          */
915         public Map<String, String> sendPluginMessage(String pluginClass, Map<String, String> parameters) throws IOException, FcpException {
916                 return sendPluginMessage(pluginClass, parameters, 0, null);
917         }
918
919         /**
920          * Sends a message to a plugin and waits for the response.
921          *
922          * @param pluginClass
923          *            The name of the plugin class
924          * @param parameters
925          *            The parameters for the plugin
926          * @param dataLength
927          *            The length of the optional data stream, or {@code 0} if there
928          *            is no optional data stream
929          * @param dataInputStream
930          *            The input stream for the payload, or {@code null} if there is
931          *            no payload
932          * @return The responses from the plugin
933          * @throws FcpException
934          *             if an FCP error occurs
935          * @throws IOException
936          *             if an I/O error occurs
937          */
938         public Map<String, String> sendPluginMessage(final String pluginClass, final Map<String, String> parameters, final long dataLength, final InputStream dataInputStream) throws IOException, FcpException {
939                 final Map<String, String> pluginReplies = Collections.synchronizedMap(new HashMap<String, String>());
940                 new ExtendedFcpAdapter() {
941
942                         @SuppressWarnings("synthetic-access")
943                         private final String identifier = createIdentifier("FCPPluginMessage");
944
945                         @Override
946                         @SuppressWarnings("synthetic-access")
947                         public void run() throws IOException {
948                                 FCPPluginMessage fcpPluginMessage = new FCPPluginMessage(pluginClass);
949                                 for (Entry<String, String> parameter : parameters.entrySet()) {
950                                         fcpPluginMessage.setParameter(parameter.getKey(), parameter.getValue());
951                                 }
952                                 fcpPluginMessage.setIdentifier(identifier);
953                                 if ((dataLength > 0) && (dataInputStream != null)) {
954                                         fcpPluginMessage.setDataLength(dataLength);
955                                         fcpPluginMessage.setPayloadInputStream(dataInputStream);
956                                 }
957                                 fcpConnection.sendMessage(fcpPluginMessage);
958                         }
959
960                         /**
961                          * {@inheritDoc}
962                          */
963                         @Override
964                         public void receivedFCPPluginReply(FcpConnection fcpConnection, FCPPluginReply fcpPluginReply) {
965                                 if (!fcpPluginReply.getIdentifier().equals(identifier)) {
966                                         return;
967                                 }
968                                 pluginReplies.putAll(fcpPluginReply.getReplies());
969                                 completionLatch.countDown();
970                         }
971
972                 }.execute();
973                 return pluginReplies;
974         }
975
976         //
977         // NODE INFORMATION
978         //
979
980         /**
981          * Returns information about the node.
982          *
983          * @param giveOpennetRef
984          *            Whether to return the OpenNet reference
985          * @param withPrivate
986          *            Whether to return private node data
987          * @param withVolatile
988          *            Whether to return volatile node data
989          * @return Node information
990          * @throws FcpException
991          *             if an FCP error occurs
992          * @throws IOException
993          *             if an I/O error occurs
994          */
995         public NodeData getNodeInformation(final Boolean giveOpennetRef, final Boolean withPrivate, final Boolean withVolatile) throws IOException, FcpException {
996                 final ObjectWrapper<NodeData> nodeDataWrapper = new ObjectWrapper<NodeData>();
997                 new ExtendedFcpAdapter() {
998
999                         @Override
1000                         @SuppressWarnings("synthetic-access")
1001                         public void run() throws IOException {
1002                                 GetNode getNodeMessage = new GetNode(giveOpennetRef, withPrivate, withVolatile);
1003                                 fcpConnection.sendMessage(getNodeMessage);
1004                         }
1005
1006                         /**
1007                          * {@inheritDoc}
1008                          */
1009                         @Override
1010                         public void receivedNodeData(FcpConnection fcpConnection, NodeData nodeData) {
1011                                 nodeDataWrapper.set(nodeData);
1012                                 completionLatch.countDown();
1013                         }
1014                 }.execute();
1015                 return nodeDataWrapper.get();
1016         }
1017
1018         //
1019         // PRIVATE METHODS
1020         //
1021
1022         /**
1023          * Creates a unique request identifier.
1024          *
1025          * @param basename
1026          *            The basename of the request
1027          * @return The created request identifier
1028          */
1029         private String createIdentifier(String basename) {
1030                 return basename + "-" + System.currentTimeMillis() + "-" + (int) (Math.random() * Integer.MAX_VALUE);
1031         }
1032
1033         /**
1034          * Checks whether the connection is in the required state.
1035          *
1036          * @param connected
1037          *            The required connection state
1038          * @throws FcpException
1039          *             if the connection is not in the required state
1040          */
1041         private void checkConnected(boolean connected) throws FcpException {
1042                 if (this.connected != connected) {
1043                         throw new FcpException("Client is " + (connected ? "not" : "already") + " connected.");
1044                 }
1045         }
1046
1047         /**
1048          * Tells the client that it is now disconnected. This method is called by
1049          * {@link ExtendedFcpAdapter} only.
1050          */
1051         private void setDisconnected() {
1052                 connected = false;
1053         }
1054
1055         /**
1056          * Implementation of an {@link FcpListener} that can store an
1057          * {@link FcpException} and wait for the arrival of a certain command.
1058          *
1059          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
1060          */
1061         private abstract class ExtendedFcpAdapter extends FcpAdapter {
1062
1063                 /** The count down latch used to wait for completion. */
1064                 protected final CountDownLatch completionLatch = new CountDownLatch(1);
1065
1066                 /** The FCP exception, if any. */
1067                 protected FcpException fcpException;
1068
1069                 /**
1070                  * Creates a new extended FCP adapter.
1071                  */
1072                 public ExtendedFcpAdapter() {
1073                         /* do nothing. */
1074                 }
1075
1076                 /**
1077                  * Executes the FCP commands in {@link #run()}, wrapping the execution
1078                  * and catching exceptions.
1079                  *
1080                  * @throws IOException
1081                  *             if an I/O error occurs
1082                  * @throws FcpException
1083                  *             if an FCP error occurs
1084                  */
1085                 @SuppressWarnings("synthetic-access")
1086                 public void execute() throws IOException, FcpException {
1087                         checkConnected(true);
1088                         fcpConnection.addFcpListener(this);
1089                         try {
1090                                 run();
1091                                 while (true) {
1092                                         try {
1093                                                 completionLatch.await();
1094                                                 break;
1095                                         } catch (InterruptedException ie1) {
1096                                                 /* ignore, we’ll loop. */
1097                                         }
1098                                 }
1099                         } catch (IOException ioe1) {
1100                                 setDisconnected();
1101                                 throw ioe1;
1102                         } finally {
1103                                 fcpConnection.removeFcpListener(this);
1104                         }
1105                         if (fcpException != null) {
1106                                 setDisconnected();
1107                                 throw fcpException;
1108                         }
1109                 }
1110
1111                 /**
1112                  * The FCP commands that actually get executed.
1113                  *
1114                  * @throws IOException
1115                  *             if an I/O error occurs
1116                  */
1117                 public abstract void run() throws IOException;
1118
1119                 /**
1120                  * {@inheritDoc}
1121                  */
1122                 @Override
1123                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
1124                         fcpException = new FcpException("Connection closed", throwable);
1125                         completionLatch.countDown();
1126                 }
1127
1128                 /**
1129                  * {@inheritDoc}
1130                  */
1131                 @Override
1132                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
1133                         fcpException = new FcpException("Connection closed, duplicate client name");
1134                         completionLatch.countDown();
1135                 }
1136
1137                 /**
1138                  * {@inheritDoc}
1139                  */
1140                 @Override
1141                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
1142                         fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
1143                         completionLatch.countDown();
1144                 }
1145
1146         }
1147
1148 }