3014aa9ccb8e674ccf4ca095e94957a02b7ac196
[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.HashSet;
27 import java.util.Set;
28 import java.util.concurrent.CountDownLatch;
29
30 import net.pterodactylus.fcp.AddPeer;
31 import net.pterodactylus.fcp.ClientHello;
32 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
33 import net.pterodactylus.fcp.EndListPeers;
34 import net.pterodactylus.fcp.FcpAdapter;
35 import net.pterodactylus.fcp.FcpConnection;
36 import net.pterodactylus.fcp.FcpListener;
37 import net.pterodactylus.fcp.ListPeers;
38 import net.pterodactylus.fcp.ModifyPeer;
39 import net.pterodactylus.fcp.NodeHello;
40 import net.pterodactylus.fcp.NodeRef;
41 import net.pterodactylus.fcp.Peer;
42 import net.pterodactylus.fcp.PeerRemoved;
43 import net.pterodactylus.fcp.ProtocolError;
44 import net.pterodactylus.fcp.RemovePeer;
45
46 /**
47  * High-level FCP client that hides the details of the underlying FCP
48  * implementation.
49  *
50  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
51  */
52 public class FcpClient {
53
54         /** Object used for synchronization. */
55         private final Object syncObject = new Object();
56
57         /** The name of this client. */
58         private final String name;
59
60         /** The underlying FCP connection. */
61         private final FcpConnection fcpConnection;
62
63         /**
64          * Creates an FCP client with the given name.
65          *
66          * @param name
67          *            The name of the FCP client
68          * @throws UnknownHostException
69          *             if the hostname “localhost” is unknown
70          */
71         public FcpClient(String name) throws UnknownHostException {
72                 this(name, "localhost");
73         }
74
75         /**
76          * Creates an FCP client.
77          *
78          * @param name
79          *            The name of the FCP client
80          * @param hostname
81          *            The hostname of the Freenet node
82          * @throws UnknownHostException
83          *             if the given hostname can not be resolved
84          */
85         public FcpClient(String name, String hostname) throws UnknownHostException {
86                 this(name, hostname, FcpConnection.DEFAULT_PORT);
87         }
88
89         /**
90          * Creates an FCP client.
91          *
92          * @param name
93          *            The name of the FCP client
94          * @param hostname
95          *            The hostname of the Freenet node
96          * @param port
97          *            The Freenet node’s FCP port
98          * @throws UnknownHostException
99          *             if the given hostname can not be resolved
100          */
101         public FcpClient(String name, String hostname, int port) throws UnknownHostException {
102                 this(name, InetAddress.getByName(hostname), port);
103         }
104
105         /**
106          * Creates an FCP client.
107          *
108          * @param name
109          *            The name of the FCP client
110          * @param host
111          *            The host address of the Freenet node
112          */
113         public FcpClient(String name, InetAddress host) {
114                 this(name, host, FcpConnection.DEFAULT_PORT);
115         }
116
117         /**
118          * Creates an FCP client.
119          *
120          * @param name
121          *            The name of the FCP client
122          * @param host
123          *            The host address of the Freenet node
124          * @param port
125          *            The Freenet node’s FCP port
126          */
127         public FcpClient(String name, InetAddress host, int port) {
128                 this.name = name;
129                 fcpConnection = new FcpConnection(host, port);
130         }
131
132         //
133         // ACTIONS
134         //
135
136         /**
137          * Connects the FCP client.
138          *
139          * @throws IOException
140          *             if an I/O error occurs
141          * @throws FcpException
142          *             if an FCP error occurs
143          */
144         public void connect() throws IOException, FcpException {
145                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
146
147                         /**
148                          * {@inheritDoc}
149                          */
150                         @Override
151                         @SuppressWarnings("synthetic-access")
152                         public void run() throws IOException {
153                                 fcpConnection.connect();
154                                 ClientHello clientHello = new ClientHello(name);
155                                 fcpConnection.sendMessage(clientHello);
156                         }
157
158                         /**
159                          * {@inheritDoc}
160                          */
161                         @Override
162                         public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
163                                 completionLatch.countDown();
164                         }
165                 };
166                 fcpListener.execute();
167         }
168
169         /**
170          * Disconnects the FCP client.
171          */
172         public void disconnect() {
173                 synchronized (syncObject) {
174                         fcpConnection.close();
175                         syncObject.notifyAll();
176                 }
177         }
178
179         //
180         // PEER MANAGEMENT
181         //
182
183         /**
184          * Returns all peers that the node has.
185          *
186          * @param withMetadata
187          *            <code>true</code> to include peer metadata
188          * @param withVolatile
189          *            <code>true</code> to include volatile peer data
190          * @return A set containing the node’s peers
191          * @throws IOException
192          *             if an I/O error occurs
193          * @throws FcpException
194          *             if an FCP error occurs
195          */
196         public Set<Peer> getPeers(final boolean withMetadata, final boolean withVolatile) throws IOException, FcpException {
197                 final Set<Peer> peers = new HashSet<Peer>();
198                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
199
200                         /**
201                          * {@inheritDoc}
202                          */
203                         @Override
204                         @SuppressWarnings("synthetic-access")
205                         public void run() throws IOException {
206                                 fcpConnection.sendMessage(new ListPeers("list-peers", withMetadata, withVolatile));
207                         }
208
209                         /**
210                          * {@inheritDoc}
211                          */
212                         @Override
213                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
214                                 peers.add(peer);
215                         }
216
217                         /**
218                          * {@inheritDoc}
219                          */
220                         @Override
221                         public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
222                                 completionLatch.countDown();
223                         }
224                 };
225                 fcpListener.execute();
226                 return peers;
227         }
228
229         /**
230          * Adds the given peer to the node.
231          *
232          * @param peer
233          *            The peer to add
234          * @throws IOException
235          *             if an I/O error occurs
236          * @throws FcpException
237          *             if an FCP error occurs
238          */
239         public void addPeer(Peer peer) throws IOException, FcpException {
240                 addPeer(peer.getNodeRef());
241         }
242
243         /**
244          * Adds the peer defined by the noderef to the node.
245          *
246          * @param nodeRef
247          *            The noderef that defines the new peer
248          * @throws IOException
249          *             if an I/O error occurs
250          * @throws FcpException
251          *             if an FCP error occurs
252          */
253         public void addPeer(NodeRef nodeRef) throws IOException, FcpException {
254                 addPeer(new AddPeer(nodeRef));
255         }
256
257         /**
258          * Adds a peer, reading the noderef from the given URL.
259          *
260          * @param url
261          *            The URL to read the noderef from
262          * @throws IOException
263          *             if an I/O error occurs
264          * @throws FcpException
265          *             if an FCP error occurs
266          */
267         public void addPeer(URL url) throws IOException, FcpException {
268                 addPeer(new AddPeer(url));
269         }
270
271         /**
272          * Adds a peer, reading the noderef of the peer from the given file.
273          * <strong>Note:</strong> the file to read the noderef from has to reside on
274          * the same machine as the node!
275          *
276          * @param file
277          *            The name of the file containing the peer’s noderef
278          * @throws IOException
279          *             if an I/O error occurs
280          * @throws FcpException
281          *             if an FCP error occurs
282          */
283         public void addPeer(String file) throws IOException, FcpException {
284                 addPeer(new AddPeer(file));
285         }
286
287         /**
288          * Sends the given {@link AddPeer} message to the node. This method should
289          * not be called directly. Use one of {@link #addPeer(Peer)},
290          * {@link #addPeer(NodeRef)}, {@link #addPeer(URL)}, or
291          * {@link #addPeer(String)} instead.
292          *
293          * @param addPeer
294          *            The “AddPeer” message
295          * @throws IOException
296          *             if an I/O error occurs
297          * @throws FcpException
298          *             if an FCP error occurs
299          */
300         private void addPeer(final AddPeer addPeer) throws IOException, FcpException {
301                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
302
303                         /**
304                          * {@inheritDoc}
305                          */
306                         @Override
307                         @SuppressWarnings("synthetic-access")
308                         public void run() throws IOException {
309                                 fcpConnection.sendMessage(addPeer);
310                         }
311
312                         /**
313                          * {@inheritDoc}
314                          */
315                         @Override
316                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
317                                 completionLatch.countDown();
318                         }
319                 };
320                 fcpListener.execute();
321         }
322
323         /**
324          * Modifies the given peer.
325          *
326          * @param peer
327          *            The peer to modify
328          * @param allowLocalAddresses
329          *            <code>true</code> to allow local address, <code>false</code>
330          *            to not allow local address, <code>null</code> to not change
331          *            the setting
332          * @param disabled
333          *            <code>true</code> to disable the peer, <code>false</code> to
334          *            enable the peer, <code>null</code> to not change the setting
335          * @param listenOnly
336          *            <code>true</code> to enable “listen only” for the peer,
337          *            <code>false</code> to disable it, <code>null</code> to not
338          *            change it
339          * @throws IOException
340          *             if an I/O error occurs
341          * @throws FcpException
342          *             if an FCP error occurs
343          */
344         public void modifyPeer(final Peer peer, final Boolean allowLocalAddresses, final Boolean disabled, final Boolean listenOnly) throws IOException, FcpException {
345                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
346
347                         /**
348                          * {@inheritDoc}
349                          */
350                         @Override
351                         @SuppressWarnings("synthetic-access")
352                         public void run() throws IOException {
353                                 fcpConnection.sendMessage(new ModifyPeer(peer.getIdentity(), allowLocalAddresses, disabled, listenOnly));
354                         }
355
356                         /**
357                          * {@inheritDoc}
358                          */
359                         @Override
360                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
361                                 completionLatch.countDown();
362                         }
363                 };
364                 fcpListener.execute();
365         }
366
367         /**
368          * Removes the given peer.
369          *
370          * @param peer
371          *            The peer to remove
372          * @throws IOException
373          *             if an I/O error occurs
374          * @throws FcpException
375          *             if an FCP error occurs
376          */
377         public void removePeer(final Peer peer) throws IOException, FcpException {
378                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
379
380                         /**
381                          * {@inheritDoc}
382                          */
383                         @Override
384                         @SuppressWarnings("synthetic-access")
385                         public void run() throws IOException {
386                                 fcpConnection.sendMessage(new RemovePeer(peer.getIdentity()));
387                         }
388
389                         /**
390                          * {@inheritDoc}
391                          */
392                         @Override
393                         public void receivedPeerRemoved(FcpConnection fcpConnection, PeerRemoved peerRemoved) {
394                                 completionLatch.countDown();
395                         }
396                 };
397                 fcpListener.execute();
398         }
399
400         /**
401          * Implementation of an {@link FcpListener} that can store an
402          * {@link FcpException} and wait for the arrival of a certain command.
403          *
404          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
405          */
406         private abstract class ExtendedFcpAdapter extends FcpAdapter {
407
408                 /** The count down latch used to wait for completion. */
409                 protected final CountDownLatch completionLatch = new CountDownLatch(1);
410
411                 /** The FCP exception, if any. */
412                 protected FcpException fcpException;
413
414                 /**
415                  * Creates a new extended FCP adapter.
416                  */
417                 public ExtendedFcpAdapter() {
418                         /* do nothing. */
419                 }
420
421                 /**
422                  * Executes the FCP commands in {@link #run()}, wrapping the execution
423                  * and catching exceptions.
424                  *
425                  * @throws IOException
426                  *             if an I/O error occurs
427                  * @throws FcpException
428                  *             if an FCP error occurs
429                  */
430                 @SuppressWarnings("synthetic-access")
431                 public void execute() throws IOException, FcpException {
432                         fcpConnection.addFcpListener(this);
433                         try {
434                                 run();
435                                 while (true) {
436                                         try {
437                                                 completionLatch.await();
438                                                 break;
439                                         } catch (InterruptedException ie1) {
440                                                 /* ignore, we’ll loop. */
441                                         }
442                                 }
443                         } finally {
444                                 fcpConnection.removeFcpListener(this);
445                         }
446                         if (fcpException != null) {
447                                 throw fcpException;
448                         }
449                 }
450
451                 /**
452                  * The FCP commands that actually get executed.
453                  *
454                  * @throws IOException
455                  *             if an I/O error occurs
456                  */
457                 public abstract void run() throws IOException;
458
459                 /**
460                  * {@inheritDoc}
461                  */
462                 @Override
463                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
464                         fcpException = new FcpException("Connection closed", throwable);
465                         completionLatch.countDown();
466                 }
467
468                 /**
469                  * {@inheritDoc}
470                  */
471                 @Override
472                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
473                         fcpException = new FcpException("Connection closed, duplicate client name");
474                         completionLatch.countDown();
475                 }
476
477                 /**
478                  * {@inheritDoc}
479                  */
480                 @Override
481                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
482                         fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
483                         completionLatch.countDown();
484                 }
485
486         }
487
488 }