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