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