2 * jFCPlib - FcpClient.java - Copyright © 2009–2016 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.fcp.highlevel;
20 import static com.google.common.collect.FluentIterable.from;
22 import java.io.Closeable;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.net.InetAddress;
27 import java.net.UnknownHostException;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
33 import java.util.Map.Entry;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.atomic.AtomicReference;
38 import net.pterodactylus.fcp.AddPeer;
39 import net.pterodactylus.fcp.AllData;
40 import net.pterodactylus.fcp.ClientGet;
41 import net.pterodactylus.fcp.ClientHello;
42 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
43 import net.pterodactylus.fcp.DataFound;
44 import net.pterodactylus.fcp.EndListPeerNotes;
45 import net.pterodactylus.fcp.EndListPeers;
46 import net.pterodactylus.fcp.EndListPersistentRequests;
47 import net.pterodactylus.fcp.FCPPluginMessage;
48 import net.pterodactylus.fcp.FCPPluginReply;
49 import net.pterodactylus.fcp.FcpAdapter;
50 import net.pterodactylus.fcp.FcpConnection;
51 import net.pterodactylus.fcp.FcpListener;
52 import net.pterodactylus.fcp.GenerateSSK;
53 import net.pterodactylus.fcp.GetFailed;
54 import net.pterodactylus.fcp.GetNode;
55 import net.pterodactylus.fcp.ListPeerNotes;
56 import net.pterodactylus.fcp.ListPeers;
57 import net.pterodactylus.fcp.ListPersistentRequests;
58 import net.pterodactylus.fcp.ModifyPeer;
59 import net.pterodactylus.fcp.ModifyPeerNote;
60 import net.pterodactylus.fcp.NodeData;
61 import net.pterodactylus.fcp.NodeHello;
62 import net.pterodactylus.fcp.NodeRef;
63 import net.pterodactylus.fcp.Peer;
64 import net.pterodactylus.fcp.PeerNote;
65 import net.pterodactylus.fcp.PeerRemoved;
66 import net.pterodactylus.fcp.PersistentGet;
67 import net.pterodactylus.fcp.PersistentPut;
68 import net.pterodactylus.fcp.ProtocolError;
69 import net.pterodactylus.fcp.RemovePeer;
70 import net.pterodactylus.fcp.SSKKeypair;
71 import net.pterodactylus.fcp.SimpleProgress;
72 import net.pterodactylus.fcp.WatchGlobal;
74 import com.google.common.base.Predicate;
77 * High-level FCP client that hides the details of the underlying FCP
80 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
82 public class FcpClient implements Closeable {
84 /** Object used for synchronization. */
85 private final Object syncObject = new Object();
87 /** Listener management. */
88 private final FcpClientListenerManager fcpClientListenerManager = new FcpClientListenerManager(this);
90 /** The underlying FCP connection. */
91 private final FcpConnection fcpConnection;
93 /** The {@link NodeHello} data sent by the node on connection. */
94 private volatile NodeHello nodeHello;
96 /** Whether the client is currently connected. */
97 private volatile boolean connected;
99 /** The listener for “connection closed” events. */
100 private FcpListener connectionClosedListener;
103 * Creates an FCP client with the given name.
105 * @throws UnknownHostException
106 * if the hostname “localhost” is unknown
108 public FcpClient() throws UnknownHostException {
113 * Creates an FCP client.
116 * The hostname of the Freenet node
117 * @throws UnknownHostException
118 * if the given hostname can not be resolved
120 public FcpClient(String hostname) throws UnknownHostException {
121 this(hostname, FcpConnection.DEFAULT_PORT);
125 * Creates an FCP client.
128 * The hostname of the Freenet node
130 * The Freenet node’s FCP port
131 * @throws UnknownHostException
132 * if the given hostname can not be resolved
134 public FcpClient(String hostname, int port) throws UnknownHostException {
135 this(InetAddress.getByName(hostname), port);
139 * Creates an FCP client.
142 * The host address of the Freenet node
144 public FcpClient(InetAddress host) {
145 this(host, FcpConnection.DEFAULT_PORT);
149 * Creates an FCP client.
152 * The host address of the Freenet node
154 * The Freenet node’s FCP port
156 public FcpClient(InetAddress host, int port) {
157 this(new FcpConnection(host, port), false);
161 * Creates a new high-level FCP client that will use the given connection.
162 * This constructor will assume that the FCP connection is already
165 * @param fcpConnection
166 * The FCP connection to use
168 public FcpClient(FcpConnection fcpConnection) {
169 this(fcpConnection, true);
173 * Creates a new high-level FCP client that will use the given connection.
175 * @param fcpConnection
176 * The FCP connection to use
178 * The initial status of the FCP connection
180 public FcpClient(FcpConnection fcpConnection, boolean connected) {
181 this.fcpConnection = fcpConnection;
182 this.connected = connected;
183 connectionClosedListener = new FcpAdapter() {
189 @SuppressWarnings("synthetic-access")
190 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
191 FcpClient.this.connected = false;
192 fcpClientListenerManager.fireFcpClientDisconnected();
195 fcpConnection.addFcpListener(connectionClosedListener);
199 // LISTENER MANAGEMENT
203 * Adds an FCP listener to the underlying connection.
206 * The FCP listener to add
208 public void addFcpListener(FcpListener fcpListener) {
209 fcpConnection.addFcpListener(fcpListener);
213 * Removes an FCP listener from the underlying connection.
216 * The FCP listener to remove
218 public void removeFcpListener(FcpListener fcpListener) {
219 fcpConnection.removeFcpListener(fcpListener);
223 * Adds an FCP client listener to the list of registered listeners.
225 * @param fcpClientListener
226 * The FCP client listener to add
228 public void addFcpClientListener(FcpClientListener fcpClientListener) {
229 fcpClientListenerManager.addListener(fcpClientListener);
233 * Removes an FCP client listener from the list of registered listeners.
235 * @param fcpClientListener
236 * The FCP client listener to remove
238 public void removeFcpClientListener(FcpClientListener fcpClientListener) {
239 fcpClientListenerManager.removeListener(fcpClientListener);
247 * Returns the {@link NodeHello} object that the node returned when
250 * @return The {@code NodeHello} data container
252 public NodeHello getNodeHello() {
257 * Returns the underlying FCP connection.
259 * @return The underlying FCP connection
261 public FcpConnection getConnection() {
262 return fcpConnection;
270 * Connects the FCP client.
273 * The name of the client
274 * @throws IOException
275 * if an I/O error occurs
276 * @throws FcpException
277 * if an FCP error occurs
279 public void connect(final String name) throws IOException, FcpException {
280 checkConnected(false);
282 new ExtendedFcpAdapter() {
288 @SuppressWarnings("synthetic-access")
289 public void run() throws IOException {
290 fcpConnection.connect();
291 ClientHello clientHello = new ClientHello(name);
292 fcpConnection.sendMessage(clientHello);
293 WatchGlobal watchGlobal = new WatchGlobal(true);
294 fcpConnection.sendMessage(watchGlobal);
301 @SuppressWarnings("synthetic-access")
302 public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
303 FcpClient.this.nodeHello = nodeHello;
304 completionLatch.countDown();
310 * Returns the file with the given URI. The retrieved data will be run
311 * through Freenet’s content filter.
315 * @return The result of the get request
316 * @throws IOException
317 * if an I/O error occurs
318 * @throws FcpException
319 * if an FCP error occurs
321 public GetResult getURI(final String uri) throws IOException, FcpException {
322 return getURI(uri, true);
326 * Returns the file with the given URI.
331 * {@code true} to filter the retrieved data, {@code false}
333 * @return The result of the get request
334 * @throws IOException
335 * if an I/O error occurs
336 * @throws FcpException
337 * if an FCP error occurs
339 public GetResult getURI(final String uri, final boolean filterData) throws IOException, FcpException {
340 checkConnected(true);
341 final GetResult getResult = new GetResult();
342 new ExtendedFcpAdapter() {
344 @SuppressWarnings("synthetic-access")
345 private final String identifier = createIdentifier("client-get");
348 @SuppressWarnings("synthetic-access")
349 public void run() throws IOException {
350 ClientGet clientGet = new ClientGet(uri, identifier);
351 clientGet.setFilterData(filterData);
352 fcpConnection.sendMessage(clientGet);
356 public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
357 if (!getFailed.getIdentifier().equals(identifier)) {
360 if ((getFailed.getCode() == 27) || (getFailed.getCode() == 24)) {
362 String newUri = getFailed.getRedirectURI();
363 getResult.realUri(newUri);
365 ClientGet clientGet = new ClientGet(newUri, identifier);
366 clientGet.setFilterData(filterData);
367 fcpConnection.sendMessage(clientGet);
368 } catch (IOException ioe1) {
369 getResult.success(false).exception(ioe1);
370 completionLatch.countDown();
373 getResult.success(false).errorCode(getFailed.getCode());
374 completionLatch.countDown();
379 public void receivedAllData(FcpConnection fcpConnection, AllData allData) {
380 if (!allData.getIdentifier().equals(identifier)) {
383 getResult.success(true).contentType(allData.getContentType()).contentLength(allData.getDataLength()).inputStream(allData.getPayloadInputStream());
384 completionLatch.countDown();
392 * Disconnects the FCP client.
394 public void disconnect() {
395 synchronized (syncObject) {
396 fcpConnection.close();
397 syncObject.notifyAll();
405 public void close() {
410 * Returns whether this client is currently connected.
412 * @return {@code true} if the client is currently connected, {@code false}
415 public boolean isConnected() {
420 * Detaches this client from its underlying FCP connection.
422 public void detach() {
423 fcpConnection.removeFcpListener(connectionClosedListener);
431 * Returns all peers that the node has.
433 * @param withMetadata
434 * <code>true</code> to include peer metadata
435 * @param withVolatile
436 * <code>true</code> to include volatile peer data
437 * @return A set containing the node’s peers
438 * @throws IOException
439 * if an I/O error occurs
440 * @throws FcpException
441 * if an FCP error occurs
443 public Collection<Peer> getPeers(final boolean withMetadata, final boolean withVolatile) throws IOException, FcpException {
444 final Set<Peer> peers = Collections.synchronizedSet(new HashSet<Peer>());
445 new ExtendedFcpAdapter() {
447 /** The ID of the “ListPeers” request. */
448 @SuppressWarnings("synthetic-access")
449 private String identifier = createIdentifier("list-peers");
455 @SuppressWarnings("synthetic-access")
456 public void run() throws IOException {
457 fcpConnection.sendMessage(new ListPeers(identifier, withMetadata, withVolatile));
464 public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
465 if (peer.getIdentifier().equals(identifier)) {
474 public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
475 if (endListPeers.getIdentifier().equals(identifier)) {
476 completionLatch.countDown();
484 * Returns all darknet peers.
486 * @param withMetadata
487 * <code>true</code> to include peer metadata
488 * @param withVolatile
489 * <code>true</code> to include volatile peer data
490 * @return A set containing the node’s darknet peers
491 * @throws IOException
492 * if an I/O error occurs
493 * @throws FcpException
494 * if an FCP error occurs
496 public Collection<Peer> getDarknetPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
497 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
498 Collection<Peer> darknetPeers = new HashSet<Peer>();
499 for (Peer peer : allPeers) {
500 if (!peer.isOpennet() && !peer.isSeed()) {
501 darknetPeers.add(peer);
508 * Returns all opennet peers.
510 * @param withMetadata
511 * <code>true</code> to include peer metadata
512 * @param withVolatile
513 * <code>true</code> to include volatile peer data
514 * @return A set containing the node’s opennet peers
515 * @throws IOException
516 * if an I/O error occurs
517 * @throws FcpException
518 * if an FCP error occurs
520 public Collection<Peer> getOpennetPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
521 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
522 Collection<Peer> opennetPeers = new HashSet<Peer>();
523 for (Peer peer : allPeers) {
524 if (peer.isOpennet() && !peer.isSeed()) {
525 opennetPeers.add(peer);
532 * Returns all seed peers.
534 * @param withMetadata
535 * <code>true</code> to include peer metadata
536 * @param withVolatile
537 * <code>true</code> to include volatile peer data
538 * @return A set containing the node’s seed peers
539 * @throws IOException
540 * if an I/O error occurs
541 * @throws FcpException
542 * if an FCP error occurs
544 public Collection<Peer> getSeedPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
545 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
546 Collection<Peer> seedPeers = new HashSet<Peer>();
547 for (Peer peer : allPeers) {
556 * Adds the given peer to the node.
560 * @throws IOException
561 * if an I/O error occurs
562 * @throws FcpException
563 * if an FCP error occurs
565 public void addPeer(Peer peer) throws IOException, FcpException {
566 addPeer(peer.getNodeRef());
570 * Adds the peer defined by the noderef to the node.
573 * The noderef that defines the new peer
574 * @throws IOException
575 * if an I/O error occurs
576 * @throws FcpException
577 * if an FCP error occurs
579 public void addPeer(NodeRef nodeRef) throws IOException, FcpException {
580 addPeer(new AddPeer(nodeRef));
584 * Adds a peer, reading the noderef from the given URL.
587 * The URL to read the noderef from
588 * @throws IOException
589 * if an I/O error occurs
590 * @throws FcpException
591 * if an FCP error occurs
593 public void addPeer(URL url) throws IOException, FcpException {
594 addPeer(new AddPeer(url));
598 * Adds a peer, reading the noderef of the peer from the given file.
599 * <strong>Note:</strong> the file to read the noderef from has to reside
600 * on the same machine as the node!
603 * The name of the file containing the peer’s noderef
604 * @throws IOException
605 * if an I/O error occurs
606 * @throws FcpException
607 * if an FCP error occurs
609 public void addPeer(String file) throws IOException, FcpException {
610 addPeer(new AddPeer(file));
614 * Sends the given {@link AddPeer} message to the node. This method should
615 * not be called directly. Use one of {@link #addPeer(Peer)},
616 * {@link #addPeer(NodeRef)}, {@link #addPeer(URL)}, or
617 * {@link #addPeer(String)} instead.
620 * The “AddPeer” message
621 * @throws IOException
622 * if an I/O error occurs
623 * @throws FcpException
624 * if an FCP error occurs
626 private void addPeer(final AddPeer addPeer) throws IOException, FcpException {
627 new ExtendedFcpAdapter() {
633 @SuppressWarnings("synthetic-access")
634 public void run() throws IOException {
635 fcpConnection.sendMessage(addPeer);
642 public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
643 completionLatch.countDown();
649 * Modifies the given peer.
653 * @param allowLocalAddresses
654 * <code>true</code> to allow local address, <code>false</code>
655 * to not allow local address, <code>null</code> to not change
658 * <code>true</code> to disable the peer, <code>false</code> to
659 * enable the peer, <code>null</code> to not change the setting
661 * <code>true</code> to enable “listen only” for the peer,
662 * <code>false</code> to disable it, <code>null</code> to not
664 * @throws IOException
665 * if an I/O error occurs
666 * @throws FcpException
667 * if an FCP error occurs
669 public void modifyPeer(final Peer peer, final Boolean allowLocalAddresses, final Boolean disabled, final Boolean listenOnly) throws IOException, FcpException {
670 new ExtendedFcpAdapter() {
676 @SuppressWarnings("synthetic-access")
677 public void run() throws IOException {
678 fcpConnection.sendMessage(new ModifyPeer(peer.getIdentity(), allowLocalAddresses, disabled, listenOnly));
685 public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
686 completionLatch.countDown();
692 * Removes the given peer.
696 * @throws IOException
697 * if an I/O error occurs
698 * @throws FcpException
699 * if an FCP error occurs
701 public void removePeer(final Peer peer) throws IOException, FcpException {
702 new ExtendedFcpAdapter() {
708 @SuppressWarnings("synthetic-access")
709 public void run() throws IOException {
710 fcpConnection.sendMessage(new RemovePeer(peer.getIdentity()));
717 public void receivedPeerRemoved(FcpConnection fcpConnection, PeerRemoved peerRemoved) {
718 completionLatch.countDown();
724 // PEER NOTES MANAGEMENT
728 * Returns the peer note of the given peer.
731 * The peer to get the note for
732 * @return The peer’s note
733 * @throws IOException
734 * if an I/O error occurs
735 * @throws FcpException
736 * if an FCP error occurs
738 public PeerNote getPeerNote(final Peer peer) throws IOException, FcpException {
739 final AtomicReference<PeerNote> objectWrapper = new AtomicReference<PeerNote>();
740 new ExtendedFcpAdapter() {
746 @SuppressWarnings("synthetic-access")
747 public void run() throws IOException {
748 fcpConnection.sendMessage(new ListPeerNotes(peer.getIdentity()));
755 public void receivedPeerNote(FcpConnection fcpConnection, PeerNote peerNote) {
756 if (peerNote.getNodeIdentifier().equals(peer.getIdentity())) {
757 objectWrapper.set(peerNote);
765 public void receivedEndListPeerNotes(FcpConnection fcpConnection, EndListPeerNotes endListPeerNotes) {
766 completionLatch.countDown();
769 return objectWrapper.get();
773 * Replaces the peer note for the given peer.
778 * The new base64-encoded note text
780 * The type of the note (currently only <code>1</code> is
782 * @throws IOException
783 * if an I/O error occurs
784 * @throws FcpException
785 * if an FCP error occurs
787 public void modifyPeerNote(final Peer peer, final String noteText, final int noteType) throws IOException, FcpException {
788 new ExtendedFcpAdapter() {
794 @SuppressWarnings("synthetic-access")
795 public void run() throws IOException {
796 fcpConnection.sendMessage(new ModifyPeerNote(peer.getIdentity(), noteText, noteType));
803 public void receivedPeer(FcpConnection fcpConnection, Peer receivedPeer) {
804 if (receivedPeer.getIdentity().equals(peer.getIdentity())) {
805 completionLatch.countDown();
816 * Generates a new SSK key pair.
818 * @return The generated key pair
819 * @throws IOException
820 * if an I/O error occurs
821 * @throws FcpException
822 * if an FCP error occurs
824 public SSKKeypair generateKeyPair() throws IOException, FcpException {
825 final AtomicReference<SSKKeypair> sskKeypairWrapper = new AtomicReference<SSKKeypair>();
826 new ExtendedFcpAdapter() {
832 @SuppressWarnings("synthetic-access")
833 public void run() throws IOException {
834 fcpConnection.sendMessage(new GenerateSSK());
841 public void receivedSSKKeypair(FcpConnection fcpConnection, SSKKeypair sskKeypair) {
842 sskKeypairWrapper.set(sskKeypair);
843 completionLatch.countDown();
846 return sskKeypairWrapper.get();
850 // REQUEST MANAGEMENT
854 * Returns all currently visible persistent get requests.
857 * <code>true</code> to return get requests from the global
858 * queue, <code>false</code> to only show requests from the
860 * @return All get requests
861 * @throws IOException
862 * if an I/O error occurs
863 * @throws FcpException
864 * if an FCP error occurs
866 public Collection<Request> getGetRequests(final boolean global) throws IOException, FcpException {
867 return from(getRequests(global)).filter(new Predicate<Request>() {
869 public boolean apply(Request request) {
870 return request instanceof GetRequest;
876 * Returns all currently visible persistent put requests.
879 * <code>true</code> to return put requests from the global
880 * queue, <code>false</code> to only show requests from the
882 * @return All put requests
883 * @throws IOException
884 * if an I/O error occurs
885 * @throws FcpException
886 * if an FCP error occurs
888 public Collection<Request> getPutRequests(final boolean global) throws IOException, FcpException {
889 return from(getRequests(global)).filter(new Predicate<Request>() {
891 public boolean apply(Request request) {
892 return request instanceof PutRequest;
898 * Returns all currently visible persistent requests.
901 * <code>true</code> to return requests from the global queue,
902 * <code>false</code> to only show requests from the
904 * @return All requests
905 * @throws IOException
906 * if an I/O error occurs
907 * @throws FcpException
908 * if an FCP error occurs
910 public Collection<Request> getRequests(final boolean global) throws IOException, FcpException {
911 final Map<String, Request> requests = Collections.synchronizedMap(new HashMap<String, Request>());
912 new ExtendedFcpAdapter() {
918 @SuppressWarnings("synthetic-access")
919 public void run() throws IOException {
920 fcpConnection.sendMessage(new ListPersistentRequests());
927 public void receivedPersistentGet(FcpConnection fcpConnection, PersistentGet persistentGet) {
928 if (!persistentGet.isGlobal() || global) {
929 GetRequest getRequest = new GetRequest(persistentGet);
930 requests.put(persistentGet.getIdentifier(), getRequest);
937 * @see net.pterodactylus.fcp.FcpAdapter#receivedDataFound(net.pterodactylus.fcp.FcpConnection,
938 * net.pterodactylus.fcp.DataFound)
941 public void receivedDataFound(FcpConnection fcpConnection, DataFound dataFound) {
942 Request getRequest = requests.get(dataFound.getIdentifier());
943 if (getRequest == null) {
946 getRequest.setComplete(true);
947 getRequest.setLength(dataFound.getDataLength());
948 getRequest.setContentType(dataFound.getMetadataContentType());
954 * @see net.pterodactylus.fcp.FcpAdapter#receivedGetFailed(net.pterodactylus.fcp.FcpConnection,
955 * net.pterodactylus.fcp.GetFailed)
958 public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
959 Request getRequest = requests.get(getFailed.getIdentifier());
960 if (getRequest == null) {
963 getRequest.setComplete(true);
964 getRequest.setFailed(true);
965 getRequest.setFatal(getFailed.isFatal());
966 getRequest.setErrorCode(getFailed.getCode());
972 * @see net.pterodactylus.fcp.FcpAdapter#receivedPersistentPut(net.pterodactylus.fcp.FcpConnection,
973 * net.pterodactylus.fcp.PersistentPut)
976 public void receivedPersistentPut(FcpConnection fcpConnection, PersistentPut persistentPut) {
977 if (!persistentPut.isGlobal() || global) {
978 PutRequest putRequest = new PutRequest(persistentPut);
979 requests.put(persistentPut.getIdentifier(), putRequest);
986 * @see net.pterodactylus.fcp.FcpAdapter#receivedSimpleProgress(net.pterodactylus.fcp.FcpConnection,
987 * net.pterodactylus.fcp.SimpleProgress)
990 public void receivedSimpleProgress(FcpConnection fcpConnection, SimpleProgress simpleProgress) {
991 Request request = requests.get(simpleProgress.getIdentifier());
992 if (request == null) {
995 request.setTotalBlocks(simpleProgress.getTotal());
996 request.setRequiredBlocks(simpleProgress.getRequired());
997 request.setFailedBlocks(simpleProgress.getFailed());
998 request.setFatallyFailedBlocks(simpleProgress.getFatallyFailed());
999 request.setSucceededBlocks(simpleProgress.getSucceeded());
1000 request.setFinalizedTotal(simpleProgress.isFinalizedTotal());
1007 public void receivedEndListPersistentRequests(FcpConnection fcpConnection, EndListPersistentRequests endListPersistentRequests) {
1008 completionLatch.countDown();
1011 return requests.values();
1015 * Sends a message to a plugin and waits for the response.
1017 * @param pluginClass
1018 * The name of the plugin class
1020 * The parameters for the plugin
1021 * @return The responses from the plugin
1022 * @throws FcpException
1023 * if an FCP error occurs
1024 * @throws IOException
1025 * if an I/O error occurs
1027 public Map<String, String> sendPluginMessage(String pluginClass, Map<String, String> parameters) throws IOException, FcpException {
1028 return sendPluginMessage(pluginClass, parameters, 0, null);
1032 * Sends a message to a plugin and waits for the response.
1034 * @param pluginClass
1035 * The name of the plugin class
1037 * The parameters for the plugin
1039 * The length of the optional data stream, or {@code 0} if there
1040 * is no optional data stream
1041 * @param dataInputStream
1042 * The input stream for the payload, or {@code null} if there is
1044 * @return The responses from the plugin
1045 * @throws FcpException
1046 * if an FCP error occurs
1047 * @throws IOException
1048 * if an I/O error occurs
1050 public Map<String, String> sendPluginMessage(final String pluginClass, final Map<String, String> parameters, final long dataLength, final InputStream dataInputStream) throws IOException, FcpException {
1051 final Map<String, String> pluginReplies = Collections.synchronizedMap(new HashMap<String, String>());
1052 new ExtendedFcpAdapter() {
1054 @SuppressWarnings("synthetic-access")
1055 private final String identifier = createIdentifier("FCPPluginMessage");
1058 @SuppressWarnings("synthetic-access")
1059 public void run() throws IOException {
1060 FCPPluginMessage fcpPluginMessage = new FCPPluginMessage(pluginClass);
1061 for (Entry<String, String> parameter : parameters.entrySet()) {
1062 fcpPluginMessage.setParameter(parameter.getKey(), parameter.getValue());
1064 fcpPluginMessage.setIdentifier(identifier);
1065 if ((dataLength > 0) && (dataInputStream != null)) {
1066 fcpPluginMessage.setDataLength(dataLength);
1067 fcpPluginMessage.setPayloadInputStream(dataInputStream);
1069 fcpConnection.sendMessage(fcpPluginMessage);
1076 public void receivedFCPPluginReply(FcpConnection fcpConnection, FCPPluginReply fcpPluginReply) {
1077 if (!fcpPluginReply.getIdentifier().equals(identifier)) {
1080 pluginReplies.putAll(fcpPluginReply.getReplies());
1081 completionLatch.countDown();
1085 return pluginReplies;
1093 * Returns information about the node.
1095 * @param giveOpennetRef
1096 * Whether to return the OpenNet reference
1097 * @param withPrivate
1098 * Whether to return private node data
1099 * @param withVolatile
1100 * Whether to return volatile node data
1101 * @return Node information
1102 * @throws FcpException
1103 * if an FCP error occurs
1104 * @throws IOException
1105 * if an I/O error occurs
1107 public NodeData getNodeInformation(final Boolean giveOpennetRef, final Boolean withPrivate, final Boolean withVolatile) throws IOException, FcpException {
1108 final AtomicReference<NodeData> nodeDataWrapper = new AtomicReference<NodeData>();
1109 new ExtendedFcpAdapter() {
1112 @SuppressWarnings("synthetic-access")
1113 public void run() throws IOException {
1114 GetNode getNodeMessage = new GetNode(giveOpennetRef, withPrivate, withVolatile);
1115 fcpConnection.sendMessage(getNodeMessage);
1122 public void receivedNodeData(FcpConnection fcpConnection, NodeData nodeData) {
1123 nodeDataWrapper.set(nodeData);
1124 completionLatch.countDown();
1127 return nodeDataWrapper.get();
1135 * Creates a unique request identifier.
1138 * The basename of the request
1139 * @return The created request identifier
1141 private String createIdentifier(String basename) {
1142 return basename + "-" + System.currentTimeMillis() + "-" + (int) (Math.random() * Integer.MAX_VALUE);
1146 * Checks whether the connection is in the required state.
1149 * The required connection state
1150 * @throws FcpException
1151 * if the connection is not in the required state
1153 private void checkConnected(boolean connected) throws FcpException {
1154 if (this.connected != connected) {
1155 throw new FcpException("Client is " + (connected ? "not" : "already") + " connected.");
1160 * Tells the client that it is now disconnected. This method is called by
1161 * {@link ExtendedFcpAdapter} only.
1163 private void setDisconnected() {
1168 * Implementation of an {@link FcpListener} that can store an
1169 * {@link FcpException} and wait for the arrival of a certain command.
1171 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
1173 private abstract class ExtendedFcpAdapter extends FcpAdapter {
1175 /** The count down latch used to wait for completion. */
1176 protected final CountDownLatch completionLatch = new CountDownLatch(1);
1178 /** The FCP exception, if any. */
1179 protected FcpException fcpException;
1182 * Creates a new extended FCP adapter.
1184 public ExtendedFcpAdapter() {
1189 * Executes the FCP commands in {@link #run()}, wrapping the execution
1190 * and catching exceptions.
1192 * @throws IOException
1193 * if an I/O error occurs
1194 * @throws FcpException
1195 * if an FCP error occurs
1197 @SuppressWarnings("synthetic-access")
1198 public void execute() throws IOException, FcpException {
1199 checkConnected(true);
1200 fcpConnection.addFcpListener(this);
1205 completionLatch.await();
1207 } catch (InterruptedException ie1) {
1208 /* ignore, we’ll loop. */
1211 } catch (IOException ioe1) {
1215 fcpConnection.removeFcpListener(this);
1217 if (fcpException != null) {
1224 * The FCP commands that actually get executed.
1226 * @throws IOException
1227 * if an I/O error occurs
1229 public abstract void run() throws IOException;
1235 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
1236 fcpException = new FcpException("Connection closed", throwable);
1237 completionLatch.countDown();
1244 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
1245 fcpException = new FcpException("Connection closed, duplicate client name");
1246 completionLatch.countDown();
1253 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
1254 fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
1255 completionLatch.countDown();