78a5064aed0225adc4f6ce47f280231fa4321afb
[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          * @return A set containing the node’s peers
187          * @throws IOException
188          *             if an I/O error occurs
189          * @throws FcpException
190          *             if an FCP error occurs
191          */
192         public Set<Peer> getPeers() throws IOException, FcpException {
193                 final Set<Peer> peers = new HashSet<Peer>();
194                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
195
196                         /**
197                          * {@inheritDoc}
198                          */
199                         @Override
200                         @SuppressWarnings("synthetic-access")
201                         public void run() throws IOException {
202                                 fcpConnection.sendMessage(new ListPeers("list-peers"));
203                         }
204
205                         /**
206                          * {@inheritDoc}
207                          */
208                         @Override
209                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
210                                 peers.add(peer);
211                         }
212
213                         /**
214                          * {@inheritDoc}
215                          */
216                         @Override
217                         public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
218                                 completionLatch.countDown();
219                         }
220                 };
221                 fcpListener.execute();
222                 return peers;
223         }
224
225         /**
226          * Adds the given peer to the node.
227          *
228          * @param peer
229          *            The peer to add
230          * @throws IOException
231          *             if an I/O error occurs
232          * @throws FcpException
233          *             if an FCP error occurs
234          */
235         public void addPeer(Peer peer) throws IOException, FcpException {
236                 addPeer(peer.getNodeRef());
237         }
238
239         /**
240          * Adds the peer defined by the noderef to the node.
241          *
242          * @param nodeRef
243          *            The noderef that defines the new peer
244          * @throws IOException
245          *             if an I/O error occurs
246          * @throws FcpException
247          *             if an FCP error occurs
248          */
249         public void addPeer(NodeRef nodeRef) throws IOException, FcpException {
250                 addPeer(new AddPeer(nodeRef));
251         }
252
253         /**
254          * Adds a peer, reading the noderef from the given URL.
255          *
256          * @param url
257          *            The URL to read the noderef from
258          * @throws IOException
259          *             if an I/O error occurs
260          * @throws FcpException
261          *             if an FCP error occurs
262          */
263         public void addPeer(URL url) throws IOException, FcpException {
264                 addPeer(new AddPeer(url));
265         }
266
267         /**
268          * Adds a peer, reading the noderef of the peer from the given file.
269          * <strong>Note:</strong> the file to read the noderef from has to reside on
270          * the same machine as the node!
271          *
272          * @param file
273          *            The name of the file containing the peer’s noderef
274          * @throws IOException
275          *             if an I/O error occurs
276          * @throws FcpException
277          *             if an FCP error occurs
278          */
279         public void addPeer(String file) throws IOException, FcpException {
280                 addPeer(new AddPeer(file));
281         }
282
283         /**
284          * Sends the given {@link AddPeer} message to the node. This method should
285          * not be called directly. Use one of {@link #addPeer(Peer)},
286          * {@link #addPeer(NodeRef)}, {@link #addPeer(URL)}, or
287          * {@link #addPeer(String)} instead.
288          *
289          * @param addPeer
290          *            The “AddPeer” message
291          * @throws IOException
292          *             if an I/O error occurs
293          * @throws FcpException
294          *             if an FCP error occurs
295          */
296         private void addPeer(final AddPeer addPeer) throws IOException, FcpException {
297                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
298
299                         /**
300                          * {@inheritDoc}
301                          */
302                         @Override
303                         @SuppressWarnings("synthetic-access")
304                         public void run() throws IOException {
305                                 fcpConnection.sendMessage(addPeer);
306                         }
307
308                         /**
309                          * {@inheritDoc}
310                          */
311                         @Override
312                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
313                                 completionLatch.countDown();
314                         }
315                 };
316                 fcpListener.execute();
317         }
318
319         /**
320          * Modifies the given peer.
321          *
322          * @param peer
323          *            The peer to modify
324          * @param allowLocalAddresses
325          *            <code>true</code> to allow local address, <code>false</code>
326          *            to not allow local address, <code>null</code> to not change
327          *            the setting
328          * @param disabled
329          *            <code>true</code> to disable the peer, <code>false</code> to
330          *            enable the peer, <code>null</code> to not change the setting
331          * @param listenOnly
332          *            <code>true</code> to enable “listen only” for the peer,
333          *            <code>false</code> to disable it, <code>null</code> to not
334          *            change it
335          * @throws IOException
336          *             if an I/O error occurs
337          * @throws FcpException
338          *             if an FCP error occurs
339          */
340         public void modifyPeer(final Peer peer, final Boolean allowLocalAddresses, final Boolean disabled, final Boolean listenOnly) throws IOException, FcpException {
341                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
342
343                         /**
344                          * {@inheritDoc}
345                          */
346                         @Override
347                         @SuppressWarnings("synthetic-access")
348                         public void run() throws IOException {
349                                 fcpConnection.sendMessage(new ModifyPeer(peer.getIdentity(), allowLocalAddresses, disabled, listenOnly));
350                         }
351
352                         /**
353                          * {@inheritDoc}
354                          */
355                         @Override
356                         public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
357                                 completionLatch.countDown();
358                         }
359                 };
360                 fcpListener.execute();
361         }
362
363         /**
364          * Removes the given peer.
365          *
366          * @param peer
367          *            The peer to remove
368          * @throws IOException
369          *             if an I/O error occurs
370          * @throws FcpException
371          *             if an FCP error occurs
372          */
373         public void removePeer(final Peer peer) throws IOException, FcpException {
374                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
375
376                         /**
377                          * {@inheritDoc}
378                          */
379                         @Override
380                         @SuppressWarnings("synthetic-access")
381                         public void run() throws IOException {
382                                 fcpConnection.sendMessage(new RemovePeer(peer.getIdentity()));
383                         }
384
385                         /**
386                          * {@inheritDoc}
387                          */
388                         @Override
389                         public void receivedPeerRemoved(FcpConnection fcpConnection, PeerRemoved peerRemoved) {
390                                 completionLatch.countDown();
391                         }
392                 };
393                 fcpListener.execute();
394         }
395
396         /**
397          * Implementation of an {@link FcpListener} that can store an
398          * {@link FcpException} and wait for the arrival of a certain command.
399          *
400          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
401          */
402         private abstract class ExtendedFcpAdapter extends FcpAdapter {
403
404                 /** The count down latch used to wait for completion. */
405                 protected final CountDownLatch completionLatch = new CountDownLatch(1);
406
407                 /** The FCP exception, if any. */
408                 protected FcpException fcpException;
409
410                 /**
411                  * Creates a new extended FCP adapter.
412                  */
413                 public ExtendedFcpAdapter() {
414                         /* do nothing. */
415                 }
416
417                 /**
418                  * Executes the FCP commands in {@link #run()}, wrapping the execution
419                  * and catching exceptions.
420                  *
421                  * @throws IOException
422                  *             if an I/O error occurs
423                  * @throws FcpException
424                  *             if an FCP error occurs
425                  */
426                 @SuppressWarnings("synthetic-access")
427                 public void execute() throws IOException, FcpException {
428                         fcpConnection.addFcpListener(this);
429                         try {
430                                 run();
431                                 while (true) {
432                                         try {
433                                                 completionLatch.await();
434                                                 break;
435                                         } catch (InterruptedException ie1) {
436                                                 /* ignore, we’ll loop. */
437                                         }
438                                 }
439                         } finally {
440                                 fcpConnection.removeFcpListener(this);
441                         }
442                         if (fcpException != null) {
443                                 throw fcpException;
444                         }
445                 }
446
447                 /**
448                  * The FCP commands that actually get executed.
449                  *
450                  * @throws IOException
451                  *             if an I/O error occurs
452                  */
453                 public abstract void run() throws IOException;
454
455                 /**
456                  * {@inheritDoc}
457                  */
458                 @Override
459                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
460                         fcpException = new FcpException("Connection closed", throwable);
461                         completionLatch.countDown();
462                 }
463
464                 /**
465                  * {@inheritDoc}
466                  */
467                 @Override
468                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
469                         fcpException = new FcpException("Connection closed, duplicate client name");
470                         completionLatch.countDown();
471                 }
472
473                 /**
474                  * {@inheritDoc}
475                  */
476                 @Override
477                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
478                         fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
479                         completionLatch.countDown();
480                 }
481
482         }
483
484 }