Implement getGetRequests by using a filter.
[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 requests.
655          *
656          * @param global
657          *            <code>true</code> to return requests from the global queue,
658          *            <code>false</code> to only show requests from the client-local
659          *            queue
660          * @return All 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> getRequests(final boolean global) throws IOException, FcpException {
667                 final Map<String, Request> requests = Collections.synchronizedMap(new HashMap<String, Request>());
668                 new ExtendedFcpAdapter() {
669
670                         /**
671                          * {@inheritDoc}
672                          */
673                         @Override
674                         @SuppressWarnings("synthetic-access")
675                         public void run() throws IOException {
676                                 fcpConnection.sendMessage(new ListPersistentRequests());
677                         }
678
679                         /**
680                          * {@inheritDoc}
681                          */
682                         @Override
683                         public void receivedPersistentGet(FcpConnection fcpConnection, PersistentGet persistentGet) {
684                                 if (!persistentGet.isGlobal() || global) {
685                                         GetRequest getRequest = new GetRequest(persistentGet);
686                                         requests.put(persistentGet.getIdentifier(), getRequest);
687                                 }
688                         }
689
690                         /**
691                          * {@inheritDoc}
692                          *
693                          * @see net.pterodactylus.fcp.FcpAdapter#receivedDataFound(net.pterodactylus.fcp.FcpConnection,
694                          *      net.pterodactylus.fcp.DataFound)
695                          */
696                         @Override
697                         public void receivedDataFound(FcpConnection fcpConnection, DataFound dataFound) {
698                                 Request getRequest = requests.get(dataFound.getIdentifier());
699                                 if (getRequest == null) {
700                                         return;
701                                 }
702                                 getRequest.setComplete(true);
703                                 getRequest.setLength(dataFound.getDataLength());
704                                 getRequest.setContentType(dataFound.getMetadataContentType());
705                         }
706
707                         /**
708                          * {@inheritDoc}
709                          *
710                          * @see net.pterodactylus.fcp.FcpAdapter#receivedGetFailed(net.pterodactylus.fcp.FcpConnection,
711                          *      net.pterodactylus.fcp.GetFailed)
712                          */
713                         @Override
714                         public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
715                                 Request getRequest = requests.get(getFailed.getIdentifier());
716                                 if (getRequest == null) {
717                                         return;
718                                 }
719                                 getRequest.setComplete(true);
720                                 getRequest.setFailed(true);
721                                 getRequest.setFatal(getFailed.isFatal());
722                                 getRequest.setErrorCode(getFailed.getCode());
723                         }
724
725                         /**
726                          * {@inheritDoc}
727                          *
728                          * @see net.pterodactylus.fcp.FcpAdapter#receivedPersistentPut(net.pterodactylus.fcp.FcpConnection,
729                          *      net.pterodactylus.fcp.PersistentPut)
730                          */
731                         @Override
732                         public void receivedPersistentPut(FcpConnection fcpConnection, PersistentPut persistentPut) {
733                                 if (!persistentPut.isGlobal() || global) {
734                                         PutRequest putRequest = new PutRequest(persistentPut);
735                                         requests.put(persistentPut.getIdentifier(), putRequest);
736                                 }
737                         }
738
739                         /**
740                          * {@inheritDoc}
741                          *
742                          * @see net.pterodactylus.fcp.FcpAdapter#receivedSimpleProgress(net.pterodactylus.fcp.FcpConnection,
743                          *      net.pterodactylus.fcp.SimpleProgress)
744                          */
745                         @Override
746                         public void receivedSimpleProgress(FcpConnection fcpConnection, SimpleProgress simpleProgress) {
747                                 Request request = requests.get(simpleProgress.getIdentifier());
748                                 if (request == null) {
749                                         return;
750                                 }
751                                 request.setTotalBlocks(simpleProgress.getTotal());
752                                 request.setRequiredBlocks(simpleProgress.getRequired());
753                                 request.setFailedBlocks(simpleProgress.getFailed());
754                                 request.setFatallyFailedBlocks(simpleProgress.getFatallyFailed());
755                                 request.setSucceededBlocks(simpleProgress.getSucceeded());
756                                 request.setFinalizedTotal(simpleProgress.isFinalizedTotal());
757                         }
758
759                         /**
760                          * {@inheritDoc}
761                          */
762                         @Override
763                         public void receivedEndListPersistentRequests(FcpConnection fcpConnection, EndListPersistentRequests endListPersistentRequests) {
764                                 completionLatch.countDown();
765                         }
766                 }.execute();
767                 return requests.values();
768         }
769
770         //
771         // PRIVATE METHODS
772         //
773
774         /**
775          * Creates a unique request identifier.
776          *
777          * @param basename
778          *            The basename of the request
779          * @return The created request identifier
780          */
781         private String createIdentifier(String basename) {
782                 return basename + "-" + System.currentTimeMillis() + "-" + (int) (Math.random() * Integer.MAX_VALUE);
783         }
784
785         /**
786          * Implementation of an {@link FcpListener} that can store an
787          * {@link FcpException} and wait for the arrival of a certain command.
788          *
789          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
790          */
791         private abstract class ExtendedFcpAdapter extends FcpAdapter {
792
793                 /** The count down latch used to wait for completion. */
794                 protected final CountDownLatch completionLatch = new CountDownLatch(1);
795
796                 /** The FCP exception, if any. */
797                 protected FcpException fcpException;
798
799                 /**
800                  * Creates a new extended FCP adapter.
801                  */
802                 public ExtendedFcpAdapter() {
803                         /* do nothing. */
804                 }
805
806                 /**
807                  * Executes the FCP commands in {@link #run()}, wrapping the execution
808                  * and catching exceptions.
809                  *
810                  * @throws IOException
811                  *             if an I/O error occurs
812                  * @throws FcpException
813                  *             if an FCP error occurs
814                  */
815                 @SuppressWarnings("synthetic-access")
816                 public void execute() throws IOException, FcpException {
817                         fcpConnection.addFcpListener(this);
818                         try {
819                                 run();
820                                 while (true) {
821                                         try {
822                                                 completionLatch.await();
823                                                 break;
824                                         } catch (InterruptedException ie1) {
825                                                 /* ignore, we’ll loop. */
826                                         }
827                                 }
828                         } finally {
829                                 fcpConnection.removeFcpListener(this);
830                         }
831                         if (fcpException != null) {
832                                 throw fcpException;
833                         }
834                 }
835
836                 /**
837                  * The FCP commands that actually get executed.
838                  *
839                  * @throws IOException
840                  *             if an I/O error occurs
841                  */
842                 public abstract void run() throws IOException;
843
844                 /**
845                  * {@inheritDoc}
846                  */
847                 @Override
848                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
849                         fcpException = new FcpException("Connection closed", throwable);
850                         completionLatch.countDown();
851                 }
852
853                 /**
854                  * {@inheritDoc}
855                  */
856                 @Override
857                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
858                         fcpException = new FcpException("Connection closed, duplicate client name");
859                         completionLatch.countDown();
860                 }
861
862                 /**
863                  * {@inheritDoc}
864                  */
865                 @Override
866                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
867                         fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
868                         completionLatch.countDown();
869                 }
870
871         }
872
873 }