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