6d9e11ac1a022cbd5b6c7d2bbe474f24d7e595cd
[jFCPlib.git] / src / net / pterodactylus / fcp / highlevel / FcpClient.java
1 /*
2  * jFCPlib - FcpClient.java -
3  * Copyright © 2009 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package net.pterodactylus.fcp.highlevel;
21
22 import java.io.IOException;
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.concurrent.CountDownLatch;
33
34 import net.pterodactylus.fcp.AddPeer;
35 import net.pterodactylus.fcp.ClientHello;
36 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
37 import net.pterodactylus.fcp.DataFound;
38 import net.pterodactylus.fcp.EndListPeerNotes;
39 import net.pterodactylus.fcp.EndListPeers;
40 import net.pterodactylus.fcp.EndListPersistentRequests;
41 import net.pterodactylus.fcp.FcpAdapter;
42 import net.pterodactylus.fcp.FcpConnection;
43 import net.pterodactylus.fcp.FcpListener;
44 import net.pterodactylus.fcp.GenerateSSK;
45 import net.pterodactylus.fcp.GetFailed;
46 import net.pterodactylus.fcp.ListPeerNotes;
47 import net.pterodactylus.fcp.ListPeers;
48 import net.pterodactylus.fcp.ListPersistentRequests;
49 import net.pterodactylus.fcp.ModifyPeer;
50 import net.pterodactylus.fcp.ModifyPeerNote;
51 import net.pterodactylus.fcp.NodeHello;
52 import net.pterodactylus.fcp.NodeRef;
53 import net.pterodactylus.fcp.Peer;
54 import net.pterodactylus.fcp.PeerNote;
55 import net.pterodactylus.fcp.PeerRemoved;
56 import net.pterodactylus.fcp.PersistentGet;
57 import net.pterodactylus.fcp.PersistentPut;
58 import net.pterodactylus.fcp.ProtocolError;
59 import net.pterodactylus.fcp.RemovePeer;
60 import net.pterodactylus.fcp.SSKKeypair;
61 import net.pterodactylus.fcp.SimpleProgress;
62 import net.pterodactylus.fcp.WatchGlobal;
63 import net.pterodactylus.util.filter.Filter;
64 import net.pterodactylus.util.filter.Filters;
65 import net.pterodactylus.util.thread.ObjectWrapper;
66
67 /**
68  * High-level FCP client that hides the details of the underlying FCP
69  * implementation.
70  *
71  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
72  */
73 public class FcpClient {
74
75         /** Object used for synchronization. */
76         private final Object syncObject = new Object();
77
78         /** The name of this client. */
79         private final String name;
80
81         /** The underlying FCP connection. */
82         private final FcpConnection fcpConnection;
83
84         /** Whether the client is currently connected. */
85         private volatile boolean connected;
86
87         /**
88          * Creates an FCP client with the given name.
89          *
90          * @param name
91          *            The name of the FCP client
92          * @throws UnknownHostException
93          *             if the hostname “localhost” is unknown
94          */
95         public FcpClient(String name) throws UnknownHostException {
96                 this(name, "localhost");
97         }
98
99         /**
100          * Creates an FCP client.
101          *
102          * @param name
103          *            The name of the FCP client
104          * @param hostname
105          *            The hostname of the Freenet node
106          * @throws UnknownHostException
107          *             if the given hostname can not be resolved
108          */
109         public FcpClient(String name, String hostname) throws UnknownHostException {
110                 this(name, hostname, FcpConnection.DEFAULT_PORT);
111         }
112
113         /**
114          * Creates an FCP client.
115          *
116          * @param name
117          *            The name of the FCP client
118          * @param hostname
119          *            The hostname of the Freenet node
120          * @param port
121          *            The Freenet node’s FCP port
122          * @throws UnknownHostException
123          *             if the given hostname can not be resolved
124          */
125         public FcpClient(String name, String hostname, int port) throws UnknownHostException {
126                 this(name, InetAddress.getByName(hostname), port);
127         }
128
129         /**
130          * Creates an FCP client.
131          *
132          * @param name
133          *            The name of the FCP client
134          * @param host
135          *            The host address of the Freenet node
136          */
137         public FcpClient(String name, InetAddress host) {
138                 this(name, host, FcpConnection.DEFAULT_PORT);
139         }
140
141         /**
142          * Creates an FCP client.
143          *
144          * @param name
145          *            The name of the FCP client
146          * @param host
147          *            The host address of the Freenet node
148          * @param port
149          *            The Freenet node’s FCP port
150          */
151         public FcpClient(String name, InetAddress host, int port) {
152                 this.name = name;
153                 fcpConnection = new FcpConnection(host, port);
154         }
155
156         //
157         // ACTIONS
158         //
159
160         /**
161          * Connects the FCP client.
162          *
163          * @throws IOException
164          *             if an I/O error occurs
165          * @throws FcpException
166          *             if an FCP error occurs
167          */
168         public void connect() throws IOException, FcpException {
169                 checkConnected(false);
170                 connected = true;
171                 new ExtendedFcpAdapter() {
172
173                         /**
174                          * {@inheritDoc}
175                          */
176                         @Override
177                         @SuppressWarnings("synthetic-access")
178                         public void run() throws IOException {
179                                 fcpConnection.connect();
180                                 ClientHello clientHello = new ClientHello(name);
181                                 fcpConnection.sendMessage(clientHello);
182                                 WatchGlobal watchGlobal = new WatchGlobal(true);
183                                 fcpConnection.sendMessage(watchGlobal);
184                         }
185
186                         /**
187                          * {@inheritDoc}
188                          */
189                         @Override
190                         public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
191                                 completionLatch.countDown();
192                         }
193                 }.execute();
194         }
195
196         /**
197          * Disconnects the FCP client.
198          */
199         public void disconnect() {
200                 synchronized (syncObject) {
201                         fcpConnection.close();
202                         syncObject.notifyAll();
203                 }
204         }
205
206         /**
207          * Returns whether this client is currently connected.
208          *
209          * @return {@code true} if the client is currently connected, {@code false}
210          *         otherwise
211          */
212         public boolean isConnected() {
213                 return connected;
214         }
215
216         //
217         // PEER MANAGEMENT
218         //
219
220         /**
221          * Returns all peers that the node has.
222          *
223          * @param withMetadata
224          *            <code>true</code> to include peer metadata
225          * @param withVolatile
226          *            <code>true</code> to include volatile peer data
227          * @return A set containing the node’s peers
228          * @throws IOException
229          *             if an I/O error occurs
230          * @throws FcpException
231          *             if an FCP error occurs
232          */
233         public Collection<Peer> getPeers(final boolean withMetadata, final boolean withVolatile) throws IOException, FcpException {
234                 final Set<Peer> peers = Collections.synchronizedSet(new HashSet<Peer>());
235                 new ExtendedFcpAdapter() {
236
237                         /** The ID of the “ListPeers” request. */
238                         @SuppressWarnings("synthetic-access")
239                         private String identifier = createIdentifier("list-peers");
240
241                         /**
242                          * {@inheritDoc}
243                          */
244                         @Override
245                         @SuppressWarnings("synthetic-access")
246                         public void run() throws IOException {
247                                 fcpConnection.sendMessage(new ListPeers(identifier, withMetadata, withVolatile));
248                         }
249
250                         /**
251                          * {@inheritDoc}
252                          */
253                         @Override
254                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
255                                 if (peer.getIdentifier().equals(identifier)) {
256                                         peers.add(peer);
257                                 }
258                         }
259
260                         /**
261                          * {@inheritDoc}
262                          */
263                         @Override
264                         public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
265                                 if (endListPeers.getIdentifier().equals(identifier)) {
266                                         completionLatch.countDown();
267                                 }
268                         }
269                 }.execute();
270                 return peers;
271         }
272
273         /**
274          * Returns all darknet peers.
275          *
276          * @param withMetadata
277          *            <code>true</code> to include peer metadata
278          * @param withVolatile
279          *            <code>true</code> to include volatile peer data
280          * @return A set containing the node’s darknet peers
281          * @throws IOException
282          *             if an I/O error occurs
283          * @throws FcpException
284          *             if an FCP error occurs
285          */
286         public Collection<Peer> getDarknetPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
287                 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
288                 Collection<Peer> darknetPeers = new HashSet<Peer>();
289                 for (Peer peer : allPeers) {
290                         if (!peer.isOpennet() && !peer.isSeed()) {
291                                 darknetPeers.add(peer);
292                         }
293                 }
294                 return darknetPeers;
295         }
296
297         /**
298          * Returns all opennet peers.
299          *
300          * @param withMetadata
301          *            <code>true</code> to include peer metadata
302          * @param withVolatile
303          *            <code>true</code> to include volatile peer data
304          * @return A set containing the node’s opennet peers
305          * @throws IOException
306          *             if an I/O error occurs
307          * @throws FcpException
308          *             if an FCP error occurs
309          */
310         public Collection<Peer> getOpennetPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
311                 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
312                 Collection<Peer> opennetPeers = new HashSet<Peer>();
313                 for (Peer peer : allPeers) {
314                         if (peer.isOpennet() && !peer.isSeed()) {
315                                 opennetPeers.add(peer);
316                         }
317                 }
318                 return opennetPeers;
319         }
320
321         /**
322          * Returns all seed peers.
323          *
324          * @param withMetadata
325          *            <code>true</code> to include peer metadata
326          * @param withVolatile
327          *            <code>true</code> to include volatile peer data
328          * @return A set containing the node’s seed peers
329          * @throws IOException
330          *             if an I/O error occurs
331          * @throws FcpException
332          *             if an FCP error occurs
333          */
334         public Collection<Peer> getSeedPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
335                 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
336                 Collection<Peer> seedPeers = new HashSet<Peer>();
337                 for (Peer peer : allPeers) {
338                         if (peer.isSeed()) {
339                                 seedPeers.add(peer);
340                         }
341                 }
342                 return seedPeers;
343         }
344
345         /**
346          * Adds the given peer to the node.
347          *
348          * @param peer
349          *            The peer to add
350          * @throws IOException
351          *             if an I/O error occurs
352          * @throws FcpException
353          *             if an FCP error occurs
354          */
355         public void addPeer(Peer peer) throws IOException, FcpException {
356                 addPeer(peer.getNodeRef());
357         }
358
359         /**
360          * Adds the peer defined by the noderef to the node.
361          *
362          * @param nodeRef
363          *            The noderef that defines the new peer
364          * @throws IOException
365          *             if an I/O error occurs
366          * @throws FcpException
367          *             if an FCP error occurs
368          */
369         public void addPeer(NodeRef nodeRef) throws IOException, FcpException {
370                 addPeer(new AddPeer(nodeRef));
371         }
372
373         /**
374          * Adds a peer, reading the noderef from the given URL.
375          *
376          * @param url
377          *            The URL to read the noderef from
378          * @throws IOException
379          *             if an I/O error occurs
380          * @throws FcpException
381          *             if an FCP error occurs
382          */
383         public void addPeer(URL url) throws IOException, FcpException {
384                 addPeer(new AddPeer(url));
385         }
386
387         /**
388          * Adds a peer, reading the noderef of the peer from the given file.
389          * <strong>Note:</strong> the file to read the noderef from has to reside on
390          * the same machine as the node!
391          *
392          * @param file
393          *            The name of the file containing the peer’s noderef
394          * @throws IOException
395          *             if an I/O error occurs
396          * @throws FcpException
397          *             if an FCP error occurs
398          */
399         public void addPeer(String file) throws IOException, FcpException {
400                 addPeer(new AddPeer(file));
401         }
402
403         /**
404          * Sends the given {@link AddPeer} message to the node. This method should
405          * not be called directly. Use one of {@link #addPeer(Peer)},
406          * {@link #addPeer(NodeRef)}, {@link #addPeer(URL)}, or
407          * {@link #addPeer(String)} instead.
408          *
409          * @param addPeer
410          *            The “AddPeer” message
411          * @throws IOException
412          *             if an I/O error occurs
413          * @throws FcpException
414          *             if an FCP error occurs
415          */
416         private void addPeer(final AddPeer addPeer) throws IOException, FcpException {
417                 new ExtendedFcpAdapter() {
418
419                         /**
420                          * {@inheritDoc}
421                          */
422                         @Override
423                         @SuppressWarnings("synthetic-access")
424                         public void run() throws IOException {
425                                 fcpConnection.sendMessage(addPeer);
426                         }
427
428                         /**
429                          * {@inheritDoc}
430                          */
431                         @Override
432                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
433                                 completionLatch.countDown();
434                         }
435                 }.execute();
436         }
437
438         /**
439          * Modifies the given peer.
440          *
441          * @param peer
442          *            The peer to modify
443          * @param allowLocalAddresses
444          *            <code>true</code> to allow local address, <code>false</code>
445          *            to not allow local address, <code>null</code> to not change
446          *            the setting
447          * @param disabled
448          *            <code>true</code> to disable the peer, <code>false</code> to
449          *            enable the peer, <code>null</code> to not change the setting
450          * @param listenOnly
451          *            <code>true</code> to enable “listen only” for the peer,
452          *            <code>false</code> to disable it, <code>null</code> to not
453          *            change it
454          * @throws IOException
455          *             if an I/O error occurs
456          * @throws FcpException
457          *             if an FCP error occurs
458          */
459         public void modifyPeer(final Peer peer, final Boolean allowLocalAddresses, final Boolean disabled, final Boolean listenOnly) throws IOException, FcpException {
460                 new ExtendedFcpAdapter() {
461
462                         /**
463                          * {@inheritDoc}
464                          */
465                         @Override
466                         @SuppressWarnings("synthetic-access")
467                         public void run() throws IOException {
468                                 fcpConnection.sendMessage(new ModifyPeer(peer.getIdentity(), allowLocalAddresses, disabled, listenOnly));
469                         }
470
471                         /**
472                          * {@inheritDoc}
473                          */
474                         @Override
475                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
476                                 completionLatch.countDown();
477                         }
478                 }.execute();
479         }
480
481         /**
482          * Removes the given peer.
483          *
484          * @param peer
485          *            The peer to remove
486          * @throws IOException
487          *             if an I/O error occurs
488          * @throws FcpException
489          *             if an FCP error occurs
490          */
491         public void removePeer(final Peer peer) throws IOException, FcpException {
492                 new ExtendedFcpAdapter() {
493
494                         /**
495                          * {@inheritDoc}
496                          */
497                         @Override
498                         @SuppressWarnings("synthetic-access")
499                         public void run() throws IOException {
500                                 fcpConnection.sendMessage(new RemovePeer(peer.getIdentity()));
501                         }
502
503                         /**
504                          * {@inheritDoc}
505                          */
506                         @Override
507                         public void receivedPeerRemoved(FcpConnection fcpConnection, PeerRemoved peerRemoved) {
508                                 completionLatch.countDown();
509                         }
510                 }.execute();
511         }
512
513         //
514         // PEER NOTES MANAGEMENT
515         //
516
517         /**
518          * Returns the peer note of the given peer.
519          *
520          * @param peer
521          *            The peer to get the note for
522          * @return The peer’s note
523          * @throws IOException
524          *             if an I/O error occurs
525          * @throws FcpException
526          *             if an FCP error occurs
527          */
528         public PeerNote getPeerNote(final Peer peer) throws IOException, FcpException {
529                 final ObjectWrapper<PeerNote> objectWrapper = new ObjectWrapper<PeerNote>();
530                 new ExtendedFcpAdapter() {
531
532                         /**
533                          * {@inheritDoc}
534                          */
535                         @Override
536                         @SuppressWarnings("synthetic-access")
537                         public void run() throws IOException {
538                                 fcpConnection.sendMessage(new ListPeerNotes(peer.getIdentity()));
539                         }
540
541                         /**
542                          * {@inheritDoc}
543                          */
544                         @Override
545                         public void receivedPeerNote(FcpConnection fcpConnection, PeerNote peerNote) {
546                                 if (peerNote.getNodeIdentifier().equals(peer.getIdentity())) {
547                                         objectWrapper.set(peerNote);
548                                 }
549                         }
550
551                         /**
552                          * {@inheritDoc}
553                          */
554                         @Override
555                         public void receivedEndListPeerNotes(FcpConnection fcpConnection, EndListPeerNotes endListPeerNotes) {
556                                 completionLatch.countDown();
557                         }
558                 }.execute();
559                 return objectWrapper.get();
560         }
561
562         /**
563          * Replaces the peer note for the given peer.
564          *
565          * @param peer
566          *            The peer
567          * @param noteText
568          *            The new base64-encoded note text
569          * @param noteType
570          *            The type of the note (currently only <code>1</code> is
571          *            allowed)
572          * @throws IOException
573          *             if an I/O error occurs
574          * @throws FcpException
575          *             if an FCP error occurs
576          */
577         public void modifyPeerNote(final Peer peer, final String noteText, final int noteType) throws IOException, FcpException {
578                 new ExtendedFcpAdapter() {
579
580                         /**
581                          * {@inheritDoc}
582                          */
583                         @Override
584                         @SuppressWarnings("synthetic-access")
585                         public void run() throws IOException {
586                                 fcpConnection.sendMessage(new ModifyPeerNote(peer.getIdentity(), noteText, noteType));
587                         }
588
589                         /**
590                          * {@inheritDoc}
591                          */
592                         @Override
593                         public void receivedPeer(FcpConnection fcpConnection, Peer receivedPeer) {
594                                 if (receivedPeer.getIdentity().equals(peer.getIdentity())) {
595                                         completionLatch.countDown();
596                                 }
597                         }
598                 }.execute();
599         }
600
601         //
602         // KEY GENERATION
603         //
604
605         /**
606          * Generates a new SSK key pair.
607          *
608          * @return The generated key pair
609          * @throws IOException
610          *             if an I/O error occurs
611          * @throws FcpException
612          *             if an FCP error occurs
613          */
614         public SSKKeypair generateKeyPair() throws IOException, FcpException {
615                 final ObjectWrapper<SSKKeypair> sskKeypairWrapper = new ObjectWrapper<SSKKeypair>();
616                 new ExtendedFcpAdapter() {
617
618                         /**
619                          * {@inheritDoc}
620                          */
621                         @Override
622                         @SuppressWarnings("synthetic-access")
623                         public void run() throws IOException {
624                                 fcpConnection.sendMessage(new GenerateSSK());
625                         }
626
627                         /**
628                          * {@inheritDoc}
629                          */
630                         @Override
631                         public void receivedSSKKeypair(FcpConnection fcpConnection, SSKKeypair sskKeypair) {
632                                 sskKeypairWrapper.set(sskKeypair);
633                                 completionLatch.countDown();
634                         }
635                 }.execute();
636                 return sskKeypairWrapper.get();
637         }
638
639         //
640         // REQUEST MANAGEMENT
641         //
642
643         /**
644          * Returns all currently visible persistent get requests.
645          *
646          * @param global
647          *            <code>true</code> to return get requests from the global
648          *            queue, <code>false</code> to only show requests from the
649          *            client-local queue
650          * @return All get requests
651          * @throws IOException
652          *             if an I/O error occurs
653          * @throws FcpException
654          *             if an FCP error occurs
655          */
656         public Collection<Request> getGetRequests(final boolean global) throws IOException, FcpException {
657                 return Filters.filteredCollection(getRequests(global), new Filter<Request>() {
658
659                         /**
660                          * {@inheritDoc}
661                          */
662                         public boolean filterObject(Request request) {
663                                 return request instanceof GetRequest;
664                         }
665                 });
666         }
667
668         /**
669          * Returns all currently visible persistent put requests.
670          *
671          * @param global
672          *            <code>true</code> to return put requests from the global
673          *            queue, <code>false</code> to only show requests from the
674          *            client-local queue
675          * @return All put requests
676          * @throws IOException
677          *             if an I/O error occurs
678          * @throws FcpException
679          *             if an FCP error occurs
680          */
681         public Collection<Request> getPutRequests(final boolean global) throws IOException, FcpException {
682                 return Filters.filteredCollection(getRequests(global), new Filter<Request>() {
683
684                         /**
685                          * {@inheritDoc}
686                          */
687                         public boolean filterObject(Request request) {
688                                 return request instanceof PutRequest;
689                         }
690                 });
691         }
692
693         /**
694          * Returns all currently visible persistent requests.
695          *
696          * @param global
697          *            <code>true</code> to return requests from the global queue,
698          *            <code>false</code> to only show requests from the client-local
699          *            queue
700          * @return All requests
701          * @throws IOException
702          *             if an I/O error occurs
703          * @throws FcpException
704          *             if an FCP error occurs
705          */
706         public Collection<Request> getRequests(final boolean global) throws IOException, FcpException {
707                 final Map<String, Request> requests = Collections.synchronizedMap(new HashMap<String, Request>());
708                 new ExtendedFcpAdapter() {
709
710                         /**
711                          * {@inheritDoc}
712                          */
713                         @Override
714                         @SuppressWarnings("synthetic-access")
715                         public void run() throws IOException {
716                                 fcpConnection.sendMessage(new ListPersistentRequests());
717                         }
718
719                         /**
720                          * {@inheritDoc}
721                          */
722                         @Override
723                         public void receivedPersistentGet(FcpConnection fcpConnection, PersistentGet persistentGet) {
724                                 if (!persistentGet.isGlobal() || global) {
725                                         GetRequest getRequest = new GetRequest(persistentGet);
726                                         requests.put(persistentGet.getIdentifier(), getRequest);
727                                 }
728                         }
729
730                         /**
731                          * {@inheritDoc}
732                          *
733                          * @see net.pterodactylus.fcp.FcpAdapter#receivedDataFound(net.pterodactylus.fcp.FcpConnection,
734                          *      net.pterodactylus.fcp.DataFound)
735                          */
736                         @Override
737                         public void receivedDataFound(FcpConnection fcpConnection, DataFound dataFound) {
738                                 Request getRequest = requests.get(dataFound.getIdentifier());
739                                 if (getRequest == null) {
740                                         return;
741                                 }
742                                 getRequest.setComplete(true);
743                                 getRequest.setLength(dataFound.getDataLength());
744                                 getRequest.setContentType(dataFound.getMetadataContentType());
745                         }
746
747                         /**
748                          * {@inheritDoc}
749                          *
750                          * @see net.pterodactylus.fcp.FcpAdapter#receivedGetFailed(net.pterodactylus.fcp.FcpConnection,
751                          *      net.pterodactylus.fcp.GetFailed)
752                          */
753                         @Override
754                         public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
755                                 Request getRequest = requests.get(getFailed.getIdentifier());
756                                 if (getRequest == null) {
757                                         return;
758                                 }
759                                 getRequest.setComplete(true);
760                                 getRequest.setFailed(true);
761                                 getRequest.setFatal(getFailed.isFatal());
762                                 getRequest.setErrorCode(getFailed.getCode());
763                         }
764
765                         /**
766                          * {@inheritDoc}
767                          *
768                          * @see net.pterodactylus.fcp.FcpAdapter#receivedPersistentPut(net.pterodactylus.fcp.FcpConnection,
769                          *      net.pterodactylus.fcp.PersistentPut)
770                          */
771                         @Override
772                         public void receivedPersistentPut(FcpConnection fcpConnection, PersistentPut persistentPut) {
773                                 if (!persistentPut.isGlobal() || global) {
774                                         PutRequest putRequest = new PutRequest(persistentPut);
775                                         requests.put(persistentPut.getIdentifier(), putRequest);
776                                 }
777                         }
778
779                         /**
780                          * {@inheritDoc}
781                          *
782                          * @see net.pterodactylus.fcp.FcpAdapter#receivedSimpleProgress(net.pterodactylus.fcp.FcpConnection,
783                          *      net.pterodactylus.fcp.SimpleProgress)
784                          */
785                         @Override
786                         public void receivedSimpleProgress(FcpConnection fcpConnection, SimpleProgress simpleProgress) {
787                                 Request request = requests.get(simpleProgress.getIdentifier());
788                                 if (request == null) {
789                                         return;
790                                 }
791                                 request.setTotalBlocks(simpleProgress.getTotal());
792                                 request.setRequiredBlocks(simpleProgress.getRequired());
793                                 request.setFailedBlocks(simpleProgress.getFailed());
794                                 request.setFatallyFailedBlocks(simpleProgress.getFatallyFailed());
795                                 request.setSucceededBlocks(simpleProgress.getSucceeded());
796                                 request.setFinalizedTotal(simpleProgress.isFinalizedTotal());
797                         }
798
799                         /**
800                          * {@inheritDoc}
801                          */
802                         @Override
803                         public void receivedEndListPersistentRequests(FcpConnection fcpConnection, EndListPersistentRequests endListPersistentRequests) {
804                                 completionLatch.countDown();
805                         }
806                 }.execute();
807                 return requests.values();
808         }
809
810         //
811         // PRIVATE METHODS
812         //
813
814         /**
815          * Creates a unique request identifier.
816          *
817          * @param basename
818          *            The basename of the request
819          * @return The created request identifier
820          */
821         private String createIdentifier(String basename) {
822                 return basename + "-" + System.currentTimeMillis() + "-" + (int) (Math.random() * Integer.MAX_VALUE);
823         }
824
825         /**
826          * Checks whether the connection is in the required state.
827          *
828          * @param connected
829          *            The required connection state
830          * @throws FcpException
831          *             if the connection is not in the required state
832          */
833         private void checkConnected(boolean connected) throws FcpException {
834                 if (this.connected != connected) {
835                         throw new FcpException("Client is " + (connected ? "not" : "already") + " connected.");
836                 }
837         }
838
839         /**
840          * Tells the client that it is now disconnected. This method is called by
841          * {@link ExtendedFcpAdapter} only.
842          */
843         private void setDisconnected() {
844                 connected = false;
845         }
846
847         /**
848          * Implementation of an {@link FcpListener} that can store an
849          * {@link FcpException} and wait for the arrival of a certain command.
850          *
851          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
852          */
853         private abstract class ExtendedFcpAdapter extends FcpAdapter {
854
855                 /** The count down latch used to wait for completion. */
856                 protected final CountDownLatch completionLatch = new CountDownLatch(1);
857
858                 /** The FCP exception, if any. */
859                 protected FcpException fcpException;
860
861                 /**
862                  * Creates a new extended FCP adapter.
863                  */
864                 public ExtendedFcpAdapter() {
865                         /* do nothing. */
866                 }
867
868                 /**
869                  * Executes the FCP commands in {@link #run()}, wrapping the execution
870                  * and catching exceptions.
871                  *
872                  * @throws IOException
873                  *             if an I/O error occurs
874                  * @throws FcpException
875                  *             if an FCP error occurs
876                  */
877                 @SuppressWarnings("synthetic-access")
878                 public void execute() throws IOException, FcpException {
879                         checkConnected(true);
880                         fcpConnection.addFcpListener(this);
881                         try {
882                                 run();
883                                 while (true) {
884                                         try {
885                                                 completionLatch.await();
886                                                 break;
887                                         } catch (InterruptedException ie1) {
888                                                 /* ignore, we’ll loop. */
889                                         }
890                                 }
891                         } catch (IOException ioe1) {
892                                 setDisconnected();
893                                 throw ioe1;
894                         } finally {
895                                 fcpConnection.removeFcpListener(this);
896                         }
897                         if (fcpException != null) {
898                                 setDisconnected();
899                                 throw fcpException;
900                         }
901                 }
902
903                 /**
904                  * The FCP commands that actually get executed.
905                  *
906                  * @throws IOException
907                  *             if an I/O error occurs
908                  */
909                 public abstract void run() throws IOException;
910
911                 /**
912                  * {@inheritDoc}
913                  */
914                 @Override
915                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
916                         fcpException = new FcpException("Connection closed", throwable);
917                         completionLatch.countDown();
918                 }
919
920                 /**
921                  * {@inheritDoc}
922                  */
923                 @Override
924                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
925                         fcpException = new FcpException("Connection closed, duplicate client name");
926                         completionLatch.countDown();
927                 }
928
929                 /**
930                  * {@inheritDoc}
931                  */
932                 @Override
933                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
934                         fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
935                         completionLatch.countDown();
936                 }
937
938         }
939
940 }