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