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