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