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