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