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;
21 import static java.util.stream.Collectors.toMap;
23 import java.io.Closeable;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.net.InetAddress;
28 import java.net.UnknownHostException;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
34 import java.util.Map.Entry;
36 import java.util.concurrent.CountDownLatch;
37 import java.util.concurrent.atomic.AtomicReference;
39 import net.pterodactylus.fcp.AddPeer;
40 import net.pterodactylus.fcp.AllData;
41 import net.pterodactylus.fcp.ClientGet;
42 import net.pterodactylus.fcp.ClientHello;
43 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
44 import net.pterodactylus.fcp.ConfigData;
45 import net.pterodactylus.fcp.DataFound;
46 import net.pterodactylus.fcp.EndListPeerNotes;
47 import net.pterodactylus.fcp.EndListPeers;
48 import net.pterodactylus.fcp.EndListPersistentRequests;
49 import net.pterodactylus.fcp.FCPPluginMessage;
50 import net.pterodactylus.fcp.FCPPluginReply;
51 import net.pterodactylus.fcp.FcpAdapter;
52 import net.pterodactylus.fcp.FcpConnection;
53 import net.pterodactylus.fcp.FcpListener;
54 import net.pterodactylus.fcp.GenerateSSK;
55 import net.pterodactylus.fcp.GetConfig;
56 import net.pterodactylus.fcp.GetFailed;
57 import net.pterodactylus.fcp.GetNode;
58 import net.pterodactylus.fcp.ListPeerNotes;
59 import net.pterodactylus.fcp.ListPeers;
60 import net.pterodactylus.fcp.ListPersistentRequests;
61 import net.pterodactylus.fcp.ModifyPeer;
62 import net.pterodactylus.fcp.ModifyPeerNote;
63 import net.pterodactylus.fcp.NodeData;
64 import net.pterodactylus.fcp.NodeHello;
65 import net.pterodactylus.fcp.NodeRef;
66 import net.pterodactylus.fcp.Peer;
67 import net.pterodactylus.fcp.PeerNote;
68 import net.pterodactylus.fcp.PeerRemoved;
69 import net.pterodactylus.fcp.PersistentGet;
70 import net.pterodactylus.fcp.PersistentPut;
71 import net.pterodactylus.fcp.ProtocolError;
72 import net.pterodactylus.fcp.RemovePeer;
73 import net.pterodactylus.fcp.SSKKeypair;
74 import net.pterodactylus.fcp.SimpleProgress;
75 import net.pterodactylus.fcp.WatchGlobal;
77 import com.google.common.base.Predicate;
80 * High-level FCP client that hides the details of the underlying FCP
83 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
85 public class FcpClient implements Closeable {
87 /** Object used for synchronization. */
88 private final Object syncObject = new Object();
90 /** Listener management. */
91 private final FcpClientListenerManager fcpClientListenerManager = new FcpClientListenerManager(this);
93 /** The underlying FCP connection. */
94 private final FcpConnection fcpConnection;
96 /** The {@link NodeHello} data sent by the node on connection. */
97 private volatile NodeHello nodeHello;
99 /** Whether the client is currently connected. */
100 private volatile boolean connected;
102 /** The listener for “connection closed” events. */
103 private FcpListener connectionClosedListener;
106 * Creates an FCP client with the given name.
108 * @throws UnknownHostException
109 * if the hostname “localhost” is unknown
111 public FcpClient() throws UnknownHostException {
116 * Creates an FCP client.
119 * The hostname of the Freenet node
120 * @throws UnknownHostException
121 * if the given hostname can not be resolved
123 public FcpClient(String hostname) throws UnknownHostException {
124 this(hostname, FcpConnection.DEFAULT_PORT);
128 * Creates an FCP client.
131 * The hostname of the Freenet node
133 * The Freenet node’s FCP port
134 * @throws UnknownHostException
135 * if the given hostname can not be resolved
137 public FcpClient(String hostname, int port) throws UnknownHostException {
138 this(InetAddress.getByName(hostname), port);
142 * Creates an FCP client.
145 * The host address of the Freenet node
147 public FcpClient(InetAddress host) {
148 this(host, FcpConnection.DEFAULT_PORT);
152 * Creates an FCP client.
155 * The host address of the Freenet node
157 * The Freenet node’s FCP port
159 public FcpClient(InetAddress host, int port) {
160 this(new FcpConnection(host, port), false);
164 * Creates a new high-level FCP client that will use the given connection.
165 * This constructor will assume that the FCP connection is already
168 * @param fcpConnection
169 * The FCP connection to use
171 public FcpClient(FcpConnection fcpConnection) {
172 this(fcpConnection, true);
176 * Creates a new high-level FCP client that will use the given connection.
178 * @param fcpConnection
179 * The FCP connection to use
181 * The initial status of the FCP connection
183 public FcpClient(FcpConnection fcpConnection, boolean connected) {
184 this.fcpConnection = fcpConnection;
185 this.connected = connected;
186 connectionClosedListener = new FcpAdapter() {
192 @SuppressWarnings("synthetic-access")
193 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
194 FcpClient.this.connected = false;
195 fcpClientListenerManager.fireFcpClientDisconnected();
198 fcpConnection.addFcpListener(connectionClosedListener);
202 // LISTENER MANAGEMENT
206 * Adds an FCP listener to the underlying connection.
209 * The FCP listener to add
211 public void addFcpListener(FcpListener fcpListener) {
212 fcpConnection.addFcpListener(fcpListener);
216 * Removes an FCP listener from the underlying connection.
219 * The FCP listener to remove
221 public void removeFcpListener(FcpListener fcpListener) {
222 fcpConnection.removeFcpListener(fcpListener);
226 * Adds an FCP client listener to the list of registered listeners.
228 * @param fcpClientListener
229 * The FCP client listener to add
231 public void addFcpClientListener(FcpClientListener fcpClientListener) {
232 fcpClientListenerManager.addListener(fcpClientListener);
236 * Removes an FCP client listener from the list of registered listeners.
238 * @param fcpClientListener
239 * The FCP client listener to remove
241 public void removeFcpClientListener(FcpClientListener fcpClientListener) {
242 fcpClientListenerManager.removeListener(fcpClientListener);
250 * Returns the {@link NodeHello} object that the node returned when
253 * @return The {@code NodeHello} data container
255 public NodeHello getNodeHello() {
260 * Returns the underlying FCP connection.
262 * @return The underlying FCP connection
264 public FcpConnection getConnection() {
265 return fcpConnection;
273 * Connects the FCP client.
276 * The name of the client
277 * @throws IOException
278 * if an I/O error occurs
279 * @throws FcpException
280 * if an FCP error occurs
282 public void connect(final String name) throws IOException, FcpException {
283 checkConnected(false);
285 new ExtendedFcpAdapter() {
291 @SuppressWarnings("synthetic-access")
292 public void run() throws IOException {
293 fcpConnection.connect();
294 ClientHello clientHello = new ClientHello(name);
295 fcpConnection.sendMessage(clientHello);
296 WatchGlobal watchGlobal = new WatchGlobal(true);
297 fcpConnection.sendMessage(watchGlobal);
304 @SuppressWarnings("synthetic-access")
305 public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
306 FcpClient.this.nodeHello = nodeHello;
313 * Returns the file with the given URI. The retrieved data will be run
314 * through Freenet’s content filter.
318 * @return The result of the get request
319 * @throws IOException
320 * if an I/O error occurs
321 * @throws FcpException
322 * if an FCP error occurs
324 public GetResult getURI(final String uri) throws IOException, FcpException {
325 return getURI(uri, true);
329 * Returns the file with the given URI.
334 * {@code true} to filter the retrieved data, {@code false}
336 * @return The result of the get request
337 * @throws IOException
338 * if an I/O error occurs
339 * @throws FcpException
340 * if an FCP error occurs
342 public GetResult getURI(final String uri, final boolean filterData) throws IOException, FcpException {
343 checkConnected(true);
344 final GetResult getResult = new GetResult();
345 new ExtendedFcpAdapter() {
347 @SuppressWarnings("synthetic-access")
348 private final String identifier = createIdentifier("client-get");
351 @SuppressWarnings("synthetic-access")
352 public void run() throws IOException {
353 ClientGet clientGet = new ClientGet(uri, identifier);
354 clientGet.setFilterData(filterData);
355 fcpConnection.sendMessage(clientGet);
359 public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
360 if (!getFailed.getIdentifier().equals(identifier)) {
363 if ((getFailed.getCode() == 27) || (getFailed.getCode() == 24)) {
365 String newUri = getFailed.getRedirectURI();
366 getResult.realUri(newUri);
368 ClientGet clientGet = new ClientGet(newUri, identifier);
369 clientGet.setFilterData(filterData);
370 fcpConnection.sendMessage(clientGet);
371 } catch (IOException ioe1) {
372 getResult.success(false).exception(ioe1);
376 getResult.success(false).errorCode(getFailed.getCode());
382 public void receivedAllData(FcpConnection fcpConnection, AllData allData) {
383 if (!allData.getIdentifier().equals(identifier)) {
386 getResult.success(true).contentType(allData.getContentType()).contentLength(allData.getDataLength()).inputStream(allData.getPayloadInputStream());
395 * Disconnects the FCP client.
397 public void disconnect() {
398 synchronized (syncObject) {
399 fcpConnection.close();
400 syncObject.notifyAll();
408 public void close() {
413 * Returns whether this client is currently connected.
415 * @return {@code true} if the client is currently connected, {@code false}
418 public boolean isConnected() {
423 * Detaches this client from its underlying FCP connection.
425 public void detach() {
426 fcpConnection.removeFcpListener(connectionClosedListener);
434 * Returns all peers that the node has.
436 * @param withMetadata
437 * <code>true</code> to include peer metadata
438 * @param withVolatile
439 * <code>true</code> to include volatile peer data
440 * @return A set containing the node’s peers
441 * @throws IOException
442 * if an I/O error occurs
443 * @throws FcpException
444 * if an FCP error occurs
446 public Collection<Peer> getPeers(final boolean withMetadata, final boolean withVolatile) throws IOException, FcpException {
447 final Set<Peer> peers = Collections.synchronizedSet(new HashSet<Peer>());
448 new ExtendedFcpAdapter() {
450 /** The ID of the “ListPeers” request. */
451 @SuppressWarnings("synthetic-access")
452 private String identifier = createIdentifier("list-peers");
458 @SuppressWarnings("synthetic-access")
459 public void run() throws IOException {
460 fcpConnection.sendMessage(new ListPeers(identifier, withMetadata, withVolatile));
467 public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
468 if (peer.getIdentifier().equals(identifier)) {
477 public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
478 if (endListPeers.getIdentifier().equals(identifier)) {
487 * Returns all darknet peers.
489 * @param withMetadata
490 * <code>true</code> to include peer metadata
491 * @param withVolatile
492 * <code>true</code> to include volatile peer data
493 * @return A set containing the node’s darknet peers
494 * @throws IOException
495 * if an I/O error occurs
496 * @throws FcpException
497 * if an FCP error occurs
499 public Collection<Peer> getDarknetPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
500 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
501 Collection<Peer> darknetPeers = new HashSet<Peer>();
502 for (Peer peer : allPeers) {
503 if (!peer.isOpennet() && !peer.isSeed()) {
504 darknetPeers.add(peer);
511 * Returns all opennet peers.
513 * @param withMetadata
514 * <code>true</code> to include peer metadata
515 * @param withVolatile
516 * <code>true</code> to include volatile peer data
517 * @return A set containing the node’s opennet peers
518 * @throws IOException
519 * if an I/O error occurs
520 * @throws FcpException
521 * if an FCP error occurs
523 public Collection<Peer> getOpennetPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
524 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
525 Collection<Peer> opennetPeers = new HashSet<Peer>();
526 for (Peer peer : allPeers) {
527 if (peer.isOpennet() && !peer.isSeed()) {
528 opennetPeers.add(peer);
535 * Returns all seed peers.
537 * @param withMetadata
538 * <code>true</code> to include peer metadata
539 * @param withVolatile
540 * <code>true</code> to include volatile peer data
541 * @return A set containing the node’s seed peers
542 * @throws IOException
543 * if an I/O error occurs
544 * @throws FcpException
545 * if an FCP error occurs
547 public Collection<Peer> getSeedPeers(boolean withMetadata, boolean withVolatile) throws IOException, FcpException {
548 Collection<Peer> allPeers = getPeers(withMetadata, withVolatile);
549 Collection<Peer> seedPeers = new HashSet<Peer>();
550 for (Peer peer : allPeers) {
559 * Adds the given peer to the node.
563 * @throws IOException
564 * if an I/O error occurs
565 * @throws FcpException
566 * if an FCP error occurs
568 public void addPeer(Peer peer) throws IOException, FcpException {
569 addPeer(peer.getNodeRef());
573 * Adds the peer defined by the noderef to the node.
576 * The noderef that defines the new peer
577 * @throws IOException
578 * if an I/O error occurs
579 * @throws FcpException
580 * if an FCP error occurs
582 public void addPeer(NodeRef nodeRef) throws IOException, FcpException {
583 addPeer(new AddPeer(nodeRef));
587 * Adds a peer, reading the noderef from the given URL.
590 * The URL to read the noderef from
591 * @throws IOException
592 * if an I/O error occurs
593 * @throws FcpException
594 * if an FCP error occurs
596 public void addPeer(URL url) throws IOException, FcpException {
597 addPeer(new AddPeer(url));
601 * Adds a peer, reading the noderef of the peer from the given file.
602 * <strong>Note:</strong> the file to read the noderef from has to reside
603 * on the same machine as the node!
606 * The name of the file containing the peer’s noderef
607 * @throws IOException
608 * if an I/O error occurs
609 * @throws FcpException
610 * if an FCP error occurs
612 public void addPeer(String file) throws IOException, FcpException {
613 addPeer(new AddPeer(file));
617 * Sends the given {@link AddPeer} message to the node. This method should
618 * not be called directly. Use one of {@link #addPeer(Peer)},
619 * {@link #addPeer(NodeRef)}, {@link #addPeer(URL)}, or
620 * {@link #addPeer(String)} instead.
623 * The “AddPeer” message
624 * @throws IOException
625 * if an I/O error occurs
626 * @throws FcpException
627 * if an FCP error occurs
629 private void addPeer(final AddPeer addPeer) throws IOException, FcpException {
630 new ExtendedFcpAdapter() {
636 @SuppressWarnings("synthetic-access")
637 public void run() throws IOException {
638 fcpConnection.sendMessage(addPeer);
645 public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
652 * Modifies the given peer.
656 * @param allowLocalAddresses
657 * <code>true</code> to allow local address, <code>false</code>
658 * to not allow local address, <code>null</code> to not change
661 * <code>true</code> to disable the peer, <code>false</code> to
662 * enable the peer, <code>null</code> to not change the setting
664 * <code>true</code> to enable “listen only” for the peer,
665 * <code>false</code> to disable it, <code>null</code> to not
667 * @throws IOException
668 * if an I/O error occurs
669 * @throws FcpException
670 * if an FCP error occurs
672 public void modifyPeer(final Peer peer, final Boolean allowLocalAddresses, final Boolean disabled, final Boolean listenOnly) throws IOException, FcpException {
673 new ExtendedFcpAdapter() {
679 @SuppressWarnings("synthetic-access")
680 public void run() throws IOException {
681 fcpConnection.sendMessage(new ModifyPeer(peer.getIdentity(), allowLocalAddresses, disabled, listenOnly));
688 public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
695 * Removes the given peer.
699 * @throws IOException
700 * if an I/O error occurs
701 * @throws FcpException
702 * if an FCP error occurs
704 public void removePeer(final Peer peer) throws IOException, FcpException {
705 new ExtendedFcpAdapter() {
711 @SuppressWarnings("synthetic-access")
712 public void run() throws IOException {
713 fcpConnection.sendMessage(new RemovePeer(peer.getIdentity()));
720 public void receivedPeerRemoved(FcpConnection fcpConnection, PeerRemoved peerRemoved) {
727 // PEER NOTES MANAGEMENT
731 * Returns the peer note of the given peer.
734 * The peer to get the note for
735 * @return The peer’s note
736 * @throws IOException
737 * if an I/O error occurs
738 * @throws FcpException
739 * if an FCP error occurs
741 public PeerNote getPeerNote(final Peer peer) throws IOException, FcpException {
742 final AtomicReference<PeerNote> objectWrapper = new AtomicReference<PeerNote>();
743 new ExtendedFcpAdapter() {
749 @SuppressWarnings("synthetic-access")
750 public void run() throws IOException {
751 fcpConnection.sendMessage(new ListPeerNotes(peer.getIdentity()));
758 public void receivedPeerNote(FcpConnection fcpConnection, PeerNote peerNote) {
759 if (peerNote.getNodeIdentifier().equals(peer.getIdentity())) {
760 objectWrapper.set(peerNote);
768 public void receivedEndListPeerNotes(FcpConnection fcpConnection, EndListPeerNotes endListPeerNotes) {
772 return objectWrapper.get();
776 * Replaces the peer note for the given peer.
781 * The new base64-encoded note text
783 * The type of the note (currently only <code>1</code> is
785 * @throws IOException
786 * if an I/O error occurs
787 * @throws FcpException
788 * if an FCP error occurs
790 public void modifyPeerNote(final Peer peer, final String noteText, final int noteType) throws IOException, FcpException {
791 new ExtendedFcpAdapter() {
797 @SuppressWarnings("synthetic-access")
798 public void run() throws IOException {
799 fcpConnection.sendMessage(new ModifyPeerNote(peer.getIdentity(), noteText, noteType));
806 public void receivedPeer(FcpConnection fcpConnection, Peer receivedPeer) {
807 if (receivedPeer.getIdentity().equals(peer.getIdentity())) {
819 * Generates a new SSK key pair.
821 * @return The generated key pair
822 * @throws IOException
823 * if an I/O error occurs
824 * @throws FcpException
825 * if an FCP error occurs
827 public SSKKeypair generateKeyPair() throws IOException, FcpException {
828 final AtomicReference<SSKKeypair> sskKeypairWrapper = new AtomicReference<SSKKeypair>();
829 new ExtendedFcpAdapter() {
835 @SuppressWarnings("synthetic-access")
836 public void run() throws IOException {
837 fcpConnection.sendMessage(new GenerateSSK());
844 public void receivedSSKKeypair(FcpConnection fcpConnection, SSKKeypair sskKeypair) {
845 sskKeypairWrapper.set(sskKeypair);
849 return sskKeypairWrapper.get();
853 // REQUEST MANAGEMENT
857 * Returns all currently visible persistent get requests.
860 * <code>true</code> to return get requests from the global
861 * queue, <code>false</code> to only show requests from the
863 * @return All get requests
864 * @throws IOException
865 * if an I/O error occurs
866 * @throws FcpException
867 * if an FCP error occurs
869 public Collection<Request> getGetRequests(final boolean global) throws IOException, FcpException {
870 return from(getRequests(global)).filter(new Predicate<Request>() {
872 public boolean apply(Request request) {
873 return request instanceof GetRequest;
879 * Returns all currently visible persistent put requests.
882 * <code>true</code> to return put requests from the global
883 * queue, <code>false</code> to only show requests from the
885 * @return All put requests
886 * @throws IOException
887 * if an I/O error occurs
888 * @throws FcpException
889 * if an FCP error occurs
891 public Collection<Request> getPutRequests(final boolean global) throws IOException, FcpException {
892 return from(getRequests(global)).filter(new Predicate<Request>() {
894 public boolean apply(Request request) {
895 return request instanceof PutRequest;
901 * Returns all currently visible persistent requests.
904 * <code>true</code> to return requests from the global queue,
905 * <code>false</code> to only show requests from the
907 * @return All requests
908 * @throws IOException
909 * if an I/O error occurs
910 * @throws FcpException
911 * if an FCP error occurs
913 public Collection<Request> getRequests(final boolean global) throws IOException, FcpException {
914 final Map<String, Request> requests = Collections.synchronizedMap(new HashMap<String, Request>());
915 new ExtendedFcpAdapter() {
921 @SuppressWarnings("synthetic-access")
922 public void run() throws IOException {
923 fcpConnection.sendMessage(new ListPersistentRequests());
930 public void receivedPersistentGet(FcpConnection fcpConnection, PersistentGet persistentGet) {
931 if (!persistentGet.isGlobal() || global) {
932 GetRequest getRequest = new GetRequest(persistentGet);
933 requests.put(persistentGet.getIdentifier(), getRequest);
940 * @see net.pterodactylus.fcp.FcpAdapter#receivedDataFound(net.pterodactylus.fcp.FcpConnection,
941 * net.pterodactylus.fcp.DataFound)
944 public void receivedDataFound(FcpConnection fcpConnection, DataFound dataFound) {
945 Request getRequest = requests.get(dataFound.getIdentifier());
946 if (getRequest == null) {
949 getRequest.setComplete(true);
950 getRequest.setLength(dataFound.getDataLength());
951 getRequest.setContentType(dataFound.getMetadataContentType());
957 * @see net.pterodactylus.fcp.FcpAdapter#receivedGetFailed(net.pterodactylus.fcp.FcpConnection,
958 * net.pterodactylus.fcp.GetFailed)
961 public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
962 Request getRequest = requests.get(getFailed.getIdentifier());
963 if (getRequest == null) {
966 getRequest.setComplete(true);
967 getRequest.setFailed(true);
968 getRequest.setFatal(getFailed.isFatal());
969 getRequest.setErrorCode(getFailed.getCode());
975 * @see net.pterodactylus.fcp.FcpAdapter#receivedPersistentPut(net.pterodactylus.fcp.FcpConnection,
976 * net.pterodactylus.fcp.PersistentPut)
979 public void receivedPersistentPut(FcpConnection fcpConnection, PersistentPut persistentPut) {
980 if (!persistentPut.isGlobal() || global) {
981 PutRequest putRequest = new PutRequest(persistentPut);
982 requests.put(persistentPut.getIdentifier(), putRequest);
989 * @see net.pterodactylus.fcp.FcpAdapter#receivedSimpleProgress(net.pterodactylus.fcp.FcpConnection,
990 * net.pterodactylus.fcp.SimpleProgress)
993 public void receivedSimpleProgress(FcpConnection fcpConnection, SimpleProgress simpleProgress) {
994 Request request = requests.get(simpleProgress.getIdentifier());
995 if (request == null) {
998 request.setTotalBlocks(simpleProgress.getTotal());
999 request.setRequiredBlocks(simpleProgress.getRequired());
1000 request.setFailedBlocks(simpleProgress.getFailed());
1001 request.setFatallyFailedBlocks(simpleProgress.getFatallyFailed());
1002 request.setSucceededBlocks(simpleProgress.getSucceeded());
1003 request.setFinalizedTotal(simpleProgress.isFinalizedTotal());
1010 public void receivedEndListPersistentRequests(FcpConnection fcpConnection, EndListPersistentRequests endListPersistentRequests) {
1014 return requests.values();
1018 * Sends a message to a plugin and waits for the response.
1020 * @param pluginClass
1021 * The name of the plugin class
1023 * The parameters for the plugin
1024 * @return The responses from the plugin
1025 * @throws FcpException
1026 * if an FCP error occurs
1027 * @throws IOException
1028 * if an I/O error occurs
1030 public Map<String, String> sendPluginMessage(String pluginClass, Map<String, String> parameters) throws IOException, FcpException {
1031 return sendPluginMessage(pluginClass, parameters, 0, null);
1035 * Sends a message to a plugin and waits for the response.
1037 * @param pluginClass
1038 * The name of the plugin class
1040 * The parameters for the plugin
1042 * The length of the optional data stream, or {@code 0} if there
1043 * is no optional data stream
1044 * @param dataInputStream
1045 * The input stream for the payload, or {@code null} if there is
1047 * @return The responses from the plugin
1048 * @throws FcpException
1049 * if an FCP error occurs
1050 * @throws IOException
1051 * if an I/O error occurs
1053 public Map<String, String> sendPluginMessage(final String pluginClass, final Map<String, String> parameters, final long dataLength, final InputStream dataInputStream) throws IOException, FcpException {
1054 final Map<String, String> pluginReplies = Collections.synchronizedMap(new HashMap<String, String>());
1055 new ExtendedFcpAdapter() {
1057 @SuppressWarnings("synthetic-access")
1058 private final String identifier = createIdentifier("FCPPluginMessage");
1061 @SuppressWarnings("synthetic-access")
1062 public void run() throws IOException {
1063 FCPPluginMessage fcpPluginMessage = new FCPPluginMessage(pluginClass);
1064 for (Entry<String, String> parameter : parameters.entrySet()) {
1065 fcpPluginMessage.setParameter(parameter.getKey(), parameter.getValue());
1067 fcpPluginMessage.setIdentifier(identifier);
1068 if ((dataLength > 0) && (dataInputStream != null)) {
1069 fcpPluginMessage.setDataLength(dataLength);
1070 fcpPluginMessage.setPayloadInputStream(dataInputStream);
1072 fcpConnection.sendMessage(fcpPluginMessage);
1079 public void receivedFCPPluginReply(FcpConnection fcpConnection, FCPPluginReply fcpPluginReply) {
1080 if (!fcpPluginReply.getIdentifier().equals(identifier)) {
1083 pluginReplies.putAll(fcpPluginReply.getReplies());
1088 return pluginReplies;
1096 * Returns information about the node.
1098 * @param giveOpennetRef
1099 * Whether to return the OpenNet reference
1100 * @param withPrivate
1101 * Whether to return private node data
1102 * @param withVolatile
1103 * Whether to return volatile node data
1104 * @return Node information
1105 * @throws FcpException
1106 * if an FCP error occurs
1107 * @throws IOException
1108 * if an I/O error occurs
1110 public NodeData getNodeInformation(final Boolean giveOpennetRef, final Boolean withPrivate, final Boolean withVolatile) throws IOException, FcpException {
1111 final AtomicReference<NodeData> nodeDataWrapper = new AtomicReference<NodeData>();
1112 new ExtendedFcpAdapter() {
1115 @SuppressWarnings("synthetic-access")
1116 public void run() throws IOException {
1117 GetNode getNodeMessage = new GetNode(giveOpennetRef, withPrivate, withVolatile);
1118 fcpConnection.sendMessage(getNodeMessage);
1125 public void receivedNodeData(FcpConnection fcpConnection, NodeData nodeData) {
1126 nodeDataWrapper.set(nodeData);
1130 return nodeDataWrapper.get();
1134 // CONFIG MANAGEMENT
1137 public Map<String, String> getConfig() throws IOException, FcpException {
1138 Map<String, String> results = new HashMap<>();
1139 new ExtendedFcpAdapter() {
1141 public void run() throws IOException {
1142 GetConfig getConfig = new GetConfig(createIdentifier("get-config"));
1143 getConfig.setWithCurrent(true);
1144 getConfig.setWithDefaults(true);
1145 getConfig.setWithShortDescription(true);
1146 getConfig.setWithLongDescription(true);
1147 getConfig.setWithDataTypes(true);
1148 getConfig.setWithExpertFlag(true);
1149 getConfig.setWithForceWriteFlag(true);
1150 getConfig.setWithSortOrder(true);
1151 fcpConnection.sendMessage(getConfig);
1155 public void receivedConfigData(FcpConnection fcpConnection, ConfigData configData) {
1156 results.putAll(filterByResponseType(configData, "current"));
1157 results.putAll(filterByResponseType(configData, "default"));
1158 results.putAll(filterByResponseType(configData, "shortDescription"));
1159 results.putAll(filterByResponseType(configData, "longDescription"));
1160 results.putAll(filterByResponseType(configData, "expertFlag"));
1161 results.putAll(filterByResponseType(configData, "dataType"));
1162 results.putAll(filterByResponseType(configData, "sortOrder"));
1163 results.putAll(filterByResponseType(configData, "forceWriteFlag"));
1167 private Map<String, String> filterByResponseType(ConfigData configData, String responseType) {
1168 return configData.getFields().entrySet().stream()
1169 .filter(e -> e.getKey().startsWith(responseType + "."))
1170 .collect(toMap(Entry::getKey, Entry::getValue));
1181 * Creates a unique request identifier.
1184 * The basename of the request
1185 * @return The created request identifier
1187 private String createIdentifier(String basename) {
1188 return basename + "-" + System.currentTimeMillis() + "-" + (int) (Math.random() * Integer.MAX_VALUE);
1192 * Checks whether the connection is in the required state.
1195 * The required connection state
1196 * @throws FcpException
1197 * if the connection is not in the required state
1199 private void checkConnected(boolean connected) throws FcpException {
1200 if (this.connected != connected) {
1201 throw new FcpException("Client is " + (connected ? "not" : "already") + " connected.");
1206 * Tells the client that it is now disconnected. This method is called by
1207 * {@link ExtendedFcpAdapter} only.
1209 private void setDisconnected() {
1214 * Implementation of an {@link FcpListener} that can store an
1215 * {@link FcpException} and wait for the arrival of a certain command.
1217 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
1219 private abstract class ExtendedFcpAdapter extends FcpAdapter {
1221 /** The count down latch used to wait for completion. */
1222 private final CountDownLatch completionLatch = new CountDownLatch(1);
1224 /** The FCP exception, if any. */
1225 protected FcpException fcpException;
1228 * Creates a new extended FCP adapter.
1230 public ExtendedFcpAdapter() {
1235 * Executes the FCP commands in {@link #run()}, wrapping the execution
1236 * and catching exceptions.
1238 * @throws IOException
1239 * if an I/O error occurs
1240 * @throws FcpException
1241 * if an FCP error occurs
1243 @SuppressWarnings("synthetic-access")
1244 public void execute() throws IOException, FcpException {
1245 checkConnected(true);
1246 fcpConnection.addFcpListener(this);
1251 completionLatch.await();
1253 } catch (InterruptedException ie1) {
1254 /* ignore, we’ll loop. */
1257 } catch (IOException ioe1) {
1261 fcpConnection.removeFcpListener(this);
1263 if (fcpException != null) {
1270 * The FCP commands that actually get executed.
1272 * @throws IOException
1273 * if an I/O error occurs
1275 public abstract void run() throws IOException;
1278 * Signals completion of the command processing.
1280 protected void complete() {
1281 completionLatch.countDown();
1288 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
1289 fcpException = new FcpException("Connection closed", throwable);
1290 completionLatch.countDown();
1297 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
1298 fcpException = new FcpException("Connection closed, duplicate client name");
1299 completionLatch.countDown();
1306 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
1307 fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
1308 completionLatch.countDown();