5a3ec091afb6182cc71c71b9acb94a320fe97b80
[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                         public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
150                                 completionLatch.countDown();
151                         }
152                 };
153                 fcpConnection.addFcpListener(fcpListener);
154                 try {
155                         fcpConnection.connect();
156                         ClientHello clientHello = new ClientHello(name);
157                         fcpConnection.sendMessage(clientHello);
158                         while (true) {
159                                 try {
160                                         fcpListener.complete();
161                                         break;
162                                 } catch (InterruptedException e) {
163                                         /* ignore, we’ll loop. */
164                                 }
165                         }
166                 } finally {
167                         fcpConnection.removeFcpListener(fcpListener);
168                 }
169                 if (fcpListener.getFcpException() != null) {
170                         throw fcpListener.getFcpException();
171                 }
172         }
173
174         /**
175          * Disconnects the FCP client.
176          */
177         public void disconnect() {
178                 synchronized (syncObject) {
179                         fcpConnection.close();
180                         syncObject.notifyAll();
181                 }
182         }
183
184         //
185         // PEER MANAGEMENT
186         //
187
188         /**
189          * Returns all peers that the node has.
190          *
191          * @return A set containing the node’s peers
192          * @throws IOException
193          *             if an I/O error occurs
194          * @throws FcpException
195          *             if an FCP error occurs
196          */
197         public Set<Peer> getPeers() throws IOException, FcpException {
198                 final Set<Peer> peers = new HashSet<Peer>();
199                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
200
201                         /**
202                          * {@inheritDoc}
203                          */
204                         @Override
205                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
206                                 peers.add(peer);
207                         }
208
209                         /**
210                          * {@inheritDoc}
211                          */
212                         @Override
213                         public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
214                                 completionLatch.countDown();
215                         }
216                 };
217                 fcpConnection.addFcpListener(fcpListener);
218                 fcpConnection.sendMessage(new ListPeers("list-peers"));
219                 try {
220                         while (true) {
221                                 try {
222                                         fcpListener.complete();
223                                         break;
224                                 } catch (InterruptedException e) {
225                                         /* ignore, we’ll loop. */
226                                 }
227                         }
228                 } finally {
229                         fcpConnection.removeFcpListener(fcpListener);
230                 }
231                 if (fcpListener.getFcpException() != null) {
232                         throw fcpListener.getFcpException();
233                 }
234                 return peers;
235         }
236
237         /**
238          * Adds the given peer to the node.
239          *
240          * @param peer
241          *            The peer to add
242          * @throws IOException
243          *             if an I/O error occurs
244          * @throws FcpException
245          *             if an FCP error occurs
246          */
247         public void addPeer(Peer peer) throws IOException, FcpException {
248                 addPeer(peer.getNodeRef());
249         }
250
251         /**
252          * Adds the peer defined by the noderef to the node.
253          *
254          * @param nodeRef
255          *            The noderef that defines the new peer
256          * @throws IOException
257          *             if an I/O error occurs
258          * @throws FcpException
259          *             if an FCP error occurs
260          */
261         public void addPeer(NodeRef nodeRef) throws IOException, FcpException {
262                 addPeer(new AddPeer(nodeRef));
263         }
264
265         /**
266          * Adds a peer, reading the noderef from the given URL.
267          *
268          * @param url
269          *            The URL to read the noderef from
270          * @throws IOException
271          *             if an I/O error occurs
272          * @throws FcpException
273          *             if an FCP error occurs
274          */
275         public void addPeer(URL url) throws IOException, FcpException {
276                 addPeer(new AddPeer(url));
277         }
278
279         /**
280          * Adds a peer, reading the noderef of the peer from the given file.
281          * <strong>Note:</strong> the file to read the noderef from has to reside on
282          * the same machine as the node!
283          *
284          * @param file
285          *            The name of the file containing the peer’s noderef
286          * @throws IOException
287          *             if an I/O error occurs
288          * @throws FcpException
289          *             if an FCP error occurs
290          */
291         public void addPeer(String file) throws IOException, FcpException {
292                 addPeer(new AddPeer(file));
293         }
294
295         /**
296          * Sends the given {@link AddPeer} message to the node. This method should
297          * not be called directly. Use one of {@link #addPeer(Peer)},
298          * {@link #addPeer(NodeRef)}, {@link #addPeer(URL)}, or
299          * {@link #addPeer(String)} instead.
300          *
301          * @param addPeer
302          *            The “AddPeer” message
303          * @throws IOException
304          *             if an I/O error occurs
305          * @throws FcpException
306          *             if an FCP error occurs
307          */
308         private void addPeer(AddPeer addPeer) throws IOException, FcpException {
309                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
310
311                         /**
312                          * {@inheritDoc}
313                          */
314                         @Override
315                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
316                                 completionLatch.countDown();
317                         }
318                 };
319                 fcpConnection.addFcpListener(fcpListener);
320                 try {
321                         fcpConnection.sendMessage(addPeer);
322                         while (true) {
323                                 try {
324                                         fcpListener.complete();
325                                         break;
326                                 } catch (InterruptedException ie1) {
327                                         /* ignore, we’ll loop. */
328                                 }
329                         }
330                 } finally {
331                         fcpConnection.removeFcpListener(fcpListener);
332                 }
333                 if (fcpListener.getFcpException() != null) {
334                         throw fcpListener.getFcpException();
335                 }
336         }
337
338         /**
339          * Modifies the given peer.
340          *
341          * @param peer
342          *            The peer to modify
343          * @param allowLocalAddresses
344          *            <code>true</code> to allow local address, <code>false</code>
345          *            to not allow local address, <code>null</code> to not change
346          *            the setting
347          * @param disabled
348          *            <code>true</code> to disable the peer, <code>false</code> to
349          *            enable the peer, <code>null</code> to not change the setting
350          * @param listenOnly
351          *            <code>true</code> to enable “listen only” for the peer,
352          *            <code>false</code> to disable it, <code>null</code> to not
353          *            change it
354          * @throws IOException
355          *             if an I/O error occurs
356          * @throws FcpException
357          *             if an FCP error occurs
358          */
359         public void modifyPeer(Peer peer, Boolean allowLocalAddresses, Boolean disabled, Boolean listenOnly) throws IOException, FcpException {
360                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
361
362                         /**
363                          * {@inheritDoc}
364                          */
365                         @Override
366                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
367                                 completionLatch.countDown();
368                         }
369                 };
370                 fcpConnection.addFcpListener(fcpListener);
371                 try {
372                         fcpConnection.sendMessage(new ModifyPeer(peer.getIdentity(), allowLocalAddresses, disabled, listenOnly));
373                 } finally {
374                         fcpConnection.removeFcpListener(fcpListener);
375                 }
376                 if (fcpListener.getFcpException() != null) {
377                         throw fcpListener.getFcpException();
378                 }
379         }
380
381         /**
382          * Implementation of an {@link FcpListener} that can store an
383          * {@link FcpException} and wait for the arrival of a certain command.
384          *
385          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
386          */
387         private static class ExtendedFcpAdapter extends FcpAdapter {
388
389                 /** The count down latch used to wait for completion. */
390                 protected final CountDownLatch completionLatch = new CountDownLatch(1);
391
392                 /** The FCP exception, if any. */
393                 protected FcpException fcpException;
394
395                 /**
396                  * Creates a new extended FCP adapter.
397                  */
398                 public ExtendedFcpAdapter() {
399                         /* do nothing. */
400                 }
401
402                 /**
403                  * Returns the FCP exception that occured. If no FCP exception occured,
404                  * <code>null</code> is returned.
405                  *
406                  * @return The FCP exception that occured, or <code>null</code>
407                  */
408                 public FcpException getFcpException() {
409                         return fcpException;
410                 }
411
412                 /**
413                  * Waits for the completion of the command.
414                  *
415                  * @throws InterruptedException
416                  *             if {@link CountDownLatch#await()} is interrupted
417                  */
418                 public void complete() throws InterruptedException {
419                         completionLatch.await();
420                 }
421
422                 /**
423                  * {@inheritDoc}
424                  */
425                 @Override
426                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
427                         fcpException = new FcpException("Connection closed", throwable);
428                         completionLatch.countDown();
429                 }
430
431                 /**
432                  * {@inheritDoc}
433                  */
434                 @Override
435                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
436                         fcpException = new FcpException("Connection closed, duplicate client name");
437                         completionLatch.countDown();
438                 }
439
440                 /**
441                  * {@inheritDoc}
442                  */
443                 @Override
444                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
445                         fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
446                         completionLatch.countDown();
447                 }
448
449         }
450
451 }