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