add setWatchGlobal
[jFCPlib.git] / src / net / pterodactylus / fcp / highlevel / HighLevelClient.java
1 /*
2  * fcplib - HighLevelClient.java -
3  * Copyright © 2008 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.BufferedReader;
23 import java.io.File;
24 import java.io.FileReader;
25 import java.io.FileWriter;
26 import java.io.IOException;
27 import java.net.InetAddress;
28 import java.net.URL;
29 import java.net.UnknownHostException;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.logging.Level;
37 import java.util.logging.Logger;
38
39 import net.pterodactylus.fcp.AddPeer;
40 import net.pterodactylus.fcp.AllData;
41 import net.pterodactylus.fcp.ClientGet;
42 import net.pterodactylus.fcp.ClientHello;
43 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
44 import net.pterodactylus.fcp.ConfigData;
45 import net.pterodactylus.fcp.DataFound;
46 import net.pterodactylus.fcp.EndListPeerNotes;
47 import net.pterodactylus.fcp.EndListPeers;
48 import net.pterodactylus.fcp.EndListPersistentRequests;
49 import net.pterodactylus.fcp.FCPPluginReply;
50 import net.pterodactylus.fcp.FcpConnection;
51 import net.pterodactylus.fcp.FcpListener;
52 import net.pterodactylus.fcp.FcpMessage;
53 import net.pterodactylus.fcp.FcpUtils;
54 import net.pterodactylus.fcp.FinishedCompression;
55 import net.pterodactylus.fcp.GenerateSSK;
56 import net.pterodactylus.fcp.GetFailed;
57 import net.pterodactylus.fcp.IdentifierCollision;
58 import net.pterodactylus.fcp.ListPeers;
59 import net.pterodactylus.fcp.ListPersistentRequests;
60 import net.pterodactylus.fcp.NodeData;
61 import net.pterodactylus.fcp.NodeHello;
62 import net.pterodactylus.fcp.NodeRef;
63 import net.pterodactylus.fcp.Peer;
64 import net.pterodactylus.fcp.PeerNote;
65 import net.pterodactylus.fcp.PeerRemoved;
66 import net.pterodactylus.fcp.PersistentGet;
67 import net.pterodactylus.fcp.PersistentPut;
68 import net.pterodactylus.fcp.PersistentPutDir;
69 import net.pterodactylus.fcp.PersistentRequestModified;
70 import net.pterodactylus.fcp.PersistentRequestRemoved;
71 import net.pterodactylus.fcp.PluginInfo;
72 import net.pterodactylus.fcp.ProtocolError;
73 import net.pterodactylus.fcp.PutFailed;
74 import net.pterodactylus.fcp.PutFetchable;
75 import net.pterodactylus.fcp.PutSuccessful;
76 import net.pterodactylus.fcp.ReturnType;
77 import net.pterodactylus.fcp.SSKKeypair;
78 import net.pterodactylus.fcp.SimpleProgress;
79 import net.pterodactylus.fcp.StartedCompression;
80 import net.pterodactylus.fcp.SubscribedUSKUpdate;
81 import net.pterodactylus.fcp.TestDDAComplete;
82 import net.pterodactylus.fcp.TestDDAReply;
83 import net.pterodactylus.fcp.TestDDARequest;
84 import net.pterodactylus.fcp.TestDDAResponse;
85 import net.pterodactylus.fcp.URIGenerated;
86 import net.pterodactylus.fcp.UnknownNodeIdentifier;
87 import net.pterodactylus.fcp.UnknownPeerNoteType;
88 import net.pterodactylus.fcp.WatchGlobal;
89
90 /**
91  * A high-level client that allows simple yet full-featured access to a Freenet
92  * node.
93  * 
94  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
95  * @version $Id$
96  */
97 public class HighLevelClient {
98
99         /** Logger. */
100         private static final Logger logger = Logger.getLogger(HighLevelClient.class.getName());
101
102         /** Object for internal synchronization. */
103         private final Object syncObject = new Object();
104
105         /** The name of the client. */
106         private final String clientName;
107
108         /** The address of the node. */
109         private InetAddress address;
110
111         /** The port number of the node. */
112         private int port;
113
114         /** The FCP connection to the node. */
115         private FcpConnection fcpConnection;
116
117         /** Listeners for high-level client events. */
118         private List<HighLevelClientListener> highLevelClientListeners = Collections.synchronizedList(new ArrayList<HighLevelClientListener>());
119
120         /** The listener for the connection. */
121         private HighLevelClientFcpListener highLevelClientFcpListener = new HighLevelClientFcpListener();
122
123         /** The callback for {@link #connect()}. */
124         private HighLevelCallback<ConnectResult> connectCallback;
125
126         /** Mapping from request identifiers to callbacks. */
127         private Map<String, HighLevelCallback<KeyGenerationResult>> keyGenerationCallbacks = Collections.synchronizedMap(new HashMap<String, HighLevelCallback<KeyGenerationResult>>());
128
129         /** Mapping from request identifier to peer list callbacks. */
130         private Map<String, HighLevelCallback<PeerListResult>> peerListCallbacks = Collections.synchronizedMap(new HashMap<String, HighLevelCallback<PeerListResult>>());
131
132         /** Mapping from request identifier to peer callbacks. */
133         private Map<String, HighLevelCallback<PeerResult>> peerCallbacks = Collections.synchronizedMap(new HashMap<String, HighLevelCallback<PeerResult>>());
134
135         /** Mapping from directories to DDA callbacks. */
136         private Map<String, HighLevelCallback<DirectDiskAccessResult>> directDiskAccessCallbacks = Collections.synchronizedMap(new HashMap<String, HighLevelCallback<DirectDiskAccessResult>>());
137
138         /** Mapping from request identifiers to download callbacks. */
139         private Map<String, HighLevelProgressCallback<DownloadResult>> downloadCallbacks = Collections.synchronizedMap(new HashMap<String, HighLevelProgressCallback<DownloadResult>>());
140
141         /** The callback for {@link #getRequests()}. */
142         private HighLevelCallback<RequestListResult> requestListCallback;
143
144         /**
145          * Creates a new high-level client that connects to a node on
146          * <code>localhost</code>.
147          * 
148          * @param clientName
149          *            The name of the client
150          * @throws UnknownHostException
151          *             if the hostname of the node can not be resolved.
152          */
153         public HighLevelClient(String clientName) throws UnknownHostException {
154                 this(clientName, "localhost");
155         }
156
157         /**
158          * Creates a new high-level client that connects to a node on the given
159          * host.
160          * 
161          * @param clientName
162          *            The name of the client
163          * @param host
164          *            The hostname of the node
165          * @throws UnknownHostException
166          *             if the hostname of the node can not be resolved.
167          */
168         public HighLevelClient(String clientName, String host) throws UnknownHostException {
169                 this(clientName, host, FcpConnection.DEFAULT_PORT);
170         }
171
172         /**
173          * Creates a new high-level client that connects to a node on the given
174          * host.
175          * 
176          * @param clientName
177          *            The name of the client
178          * @param host
179          *            The hostname of the node
180          * @param port
181          *            The port number of the node
182          * @throws UnknownHostException
183          *             if the hostname of the node can not be resolved.
184          */
185         public HighLevelClient(String clientName, String host, int port) throws UnknownHostException {
186                 this(clientName, InetAddress.getByName(host), port);
187         }
188
189         /**
190          * Creates a new high-level client that connects to a node at the given
191          * address.
192          * 
193          * @param clientName
194          *            The name of the client
195          * @param address
196          *            The address of the node
197          * @param port
198          *            The port number of the node
199          */
200         public HighLevelClient(String clientName, InetAddress address, int port) {
201                 this.clientName = clientName;
202                 this.address = address;
203                 this.port = port;
204         }
205
206         //
207         // EVENT MANAGEMENT
208         //
209
210         /**
211          * Adds the given high-level client listener to list of listeners.
212          * 
213          * @param highLevelClientListener
214          *            The listener to add
215          */
216         public void addHighLevelClientListener(HighLevelClientListener highLevelClientListener) {
217                 highLevelClientListeners.add(highLevelClientListener);
218         }
219
220         /**
221          * Removes the given high-level client listener from the list of listeners.
222          * 
223          * @param highLevelClientListener
224          *            The listener to remove
225          */
226         public void removeHighLevelClientListener(HighLevelClientListener highLevelClientListener) {
227                 highLevelClientListeners.remove(highLevelClientListener);
228         }
229
230         /**
231          * Notifies all listeners that a client has connected.
232          */
233         private void fireClientConnected() {
234                 for (HighLevelClientListener highLevelClientListener: highLevelClientListeners) {
235                         highLevelClientListener.clientConnected(this);
236                 }
237         }
238
239         /**
240          * Notifies all listeners that a client has disconnected.
241          * 
242          * @param throwable
243          *            The exception that caused the disconnect, or <code>null</code>
244          *            if there was no exception
245          */
246         private void fireClientDisconnected(Throwable throwable) {
247                 for (HighLevelClientListener highLevelClientListener: highLevelClientListeners) {
248                         highLevelClientListener.clientDisconnected(this, throwable);
249                 }
250         }
251
252         //
253         // ACCESSORS
254         //
255
256         /**
257          * Returns the FCP connection that backs this high-level client. This method
258          * should be used with care as fiddling around with the FCP connection can
259          * easily break the high-level client if you don’t know what you’re doing!
260          * 
261          * @return The FCP connection of this client
262          */
263         public FcpConnection getFcpConnection() {
264                 return fcpConnection;
265         }
266
267         //
268         // ACTIONS
269         //
270
271         /**
272          * Connects the client.
273          * 
274          * @return A callback with a connection result
275          * @throws IOException
276          *             if an I/O error occurs communicating with the node
277          */
278         public HighLevelCallback<ConnectResult> connect() throws IOException {
279                 fcpConnection = new FcpConnection(address, port);
280                 fcpConnection.addFcpListener(highLevelClientFcpListener);
281                 fcpConnection.connect();
282                 ClientHello clientHello = new ClientHello(clientName);
283                 connectCallback = new HighLevelCallback<ConnectResult>(new ConnectResult());
284                 fcpConnection.sendMessage(clientHello);
285                 return connectCallback;
286         }
287
288         /**
289          * Disconnects the client from the node.
290          */
291         public void disconnect() {
292                 disconnect(null);
293         }
294
295         /**
296          * Generates a new SSK keypair.
297          * 
298          * @return A callback with the keypair
299          * @throws IOException
300          *             if an I/O error occurs communicating with the node
301          */
302         public HighLevelCallback<KeyGenerationResult> generateKey() throws IOException {
303                 String identifier = generateIdentifier("generateSSK");
304                 GenerateSSK generateSSK = new GenerateSSK(identifier);
305                 HighLevelCallback<KeyGenerationResult> keyGenerationCallback = new HighLevelCallback<KeyGenerationResult>(new KeyGenerationResult(identifier));
306                 keyGenerationCallbacks.put(identifier, keyGenerationCallback);
307                 fcpConnection.sendMessage(generateSSK);
308                 return keyGenerationCallback;
309         }
310
311         /**
312          * Sets whether to watch the global queue.
313          * 
314          * @param enabled
315          *            <code>true</code> to watch the global queue in addition to
316          *            the client-local queue, <code>false</code> to only watch the
317          *            client-local queue
318          * @throws IOException
319          *             if an I/O error occurs communicating with the node
320          */
321         public void setWatchGlobal(boolean enabled) throws IOException {
322                 WatchGlobal watchGlobal = new WatchGlobal(enabled);
323                 fcpConnection.sendMessage(watchGlobal);
324         }
325
326         /**
327          * Gets a list of all peers from the node.
328          * 
329          * @return A callback with the peer list
330          * @throws IOException
331          *             if an I/O error occurs with the node
332          */
333         public HighLevelCallback<PeerListResult> getPeers() throws IOException {
334                 String identifier = generateIdentifier("listPeers");
335                 ListPeers listPeers = new ListPeers(identifier, true, true);
336                 HighLevelCallback<PeerListResult> peerListCallback = new HighLevelCallback<PeerListResult>(new PeerListResult(identifier));
337                 peerListCallbacks.put(identifier, peerListCallback);
338                 fcpConnection.sendMessage(listPeers);
339                 return peerListCallback;
340         }
341
342         /**
343          * Adds the peer whose noderef is stored in the given file.
344          * 
345          * @param nodeRefFile
346          *            The name of the file the peer’s noderef is stored in
347          * @return A peer callback
348          * @throws IOException
349          *             if an I/O error occurs communicating with the node
350          */
351         public HighLevelCallback<PeerResult> addPeer(String nodeRefFile) throws IOException {
352                 String identifier = generateIdentifier("addPeer");
353                 AddPeer addPeer = new AddPeer(nodeRefFile);
354                 HighLevelCallback<PeerResult> peerCallback = new HighLevelCallback<PeerResult>(new PeerResult(identifier));
355                 peerCallbacks.put(identifier, peerCallback);
356                 fcpConnection.sendMessage(addPeer);
357                 return peerCallback;
358         }
359
360         /**
361          * Adds the peer whose noderef is stored in the given file.
362          * 
363          * @param nodeRefURL
364          *            The URL where the peer’s noderef is stored
365          * @return A peer callback
366          * @throws IOException
367          *             if an I/O error occurs communicating with the node
368          */
369         public HighLevelCallback<PeerResult> addPeer(URL nodeRefURL) throws IOException {
370                 String identifier = generateIdentifier("addPeer");
371                 AddPeer addPeer = new AddPeer(nodeRefURL);
372                 HighLevelCallback<PeerResult> peerCallback = new HighLevelCallback<PeerResult>(new PeerResult(identifier));
373                 peerCallbacks.put(identifier, peerCallback);
374                 fcpConnection.sendMessage(addPeer);
375                 return peerCallback;
376         }
377
378         /**
379          * Adds the peer whose noderef is stored in the given file.
380          * 
381          * @param nodeRef
382          *            The peer’s noderef
383          * @return A peer callback
384          * @throws IOException
385          *             if an I/O error occurs communicating with the node
386          */
387         public HighLevelCallback<PeerResult> addPeer(NodeRef nodeRef) throws IOException {
388                 String identifier = generateIdentifier("addPeer");
389                 AddPeer addPeer = new AddPeer(nodeRef);
390                 HighLevelCallback<PeerResult> peerCallback = new HighLevelCallback<PeerResult>(new PeerResult(identifier));
391                 peerCallbacks.put(identifier, peerCallback);
392                 fcpConnection.sendMessage(addPeer);
393                 return peerCallback;
394         }
395
396         /**
397          * Checks whether direct disk access for the given directory is possible.
398          * You have to perform this check before you can upload or download anything
399          * from or the disk directly!
400          * 
401          * @param directory
402          *            The directory to check
403          * @param wantRead
404          *            Whether you want to read the given directory
405          * @param wantWrite
406          *            Whether you want to write to the given directory
407          * @return A direct disk access callback
408          * @throws IOException
409          */
410         public HighLevelCallback<DirectDiskAccessResult> checkDirectDiskAccess(String directory, boolean wantRead, boolean wantWrite) throws IOException {
411                 TestDDARequest testDDARequest = new TestDDARequest(directory, wantRead, wantWrite);
412                 HighLevelCallback<DirectDiskAccessResult> directDiskAccessCallback = new HighLevelCallback<DirectDiskAccessResult>(new DirectDiskAccessResult(directory));
413                 directDiskAccessCallbacks.put(directory, directDiskAccessCallback);
414                 fcpConnection.sendMessage(testDDARequest);
415                 return directDiskAccessCallback;
416         }
417
418         /**
419          * Starts a download. Files can either be download to disk or streamed from
420          * the node. When downloading to disk you have to perform a direct disk
421          * access check for the directory you want to put the downloaded file in!
422          * 
423          * @see #checkDirectDiskAccess(String, boolean, boolean)
424          * @param uri
425          *            The URI to get
426          * @param filename
427          *            The filename to save the data to, or <code>null</code> to
428          *            retrieve the data as InputStream from the
429          *            {@link DownloadResult}
430          * @param global
431          *            Whether to put the download on the global queue
432          * @return A download result
433          * @throws IOException
434          *             if an I/O error occurs communicating with the node
435          */
436         public HighLevelProgressCallback<DownloadResult> download(String uri, String filename, boolean global) throws IOException {
437                 String identifier = generateIdentifier("download");
438                 ClientGet clientGet = new ClientGet(uri, identifier, (filename == null) ? ReturnType.direct : ReturnType.disk);
439                 clientGet.setGlobal(global);
440                 HighLevelProgressCallback<DownloadResult> downloadCallback = new HighLevelProgressCallback<DownloadResult>(new DownloadResult(identifier));
441                 downloadCallbacks.put(identifier, downloadCallback);
442                 fcpConnection.sendMessage(clientGet);
443                 return downloadCallback;
444         }
445
446         /**
447          * Requests a list of all running requests from the node.
448          * 
449          * @return The request list result
450          * @throws IOException
451          *             if an I/O errors communicating with the node
452          */
453         public HighLevelCallback<RequestListResult> getRequests() throws IOException {
454                 String identifier = generateIdentifier("list-persistent-requests");
455                 ListPersistentRequests listPersistentRequests = new ListPersistentRequests();
456                 synchronized (syncObject) {
457                         if (requestListCallback != null) {
458                                 logger.log(Level.SEVERE, "getRequests() called with request still running!");
459                         }
460                         requestListCallback = new HighLevelCallback<RequestListResult>(new RequestListResult(identifier));
461                 }
462                 fcpConnection.sendMessage(listPersistentRequests);
463                 return requestListCallback;
464         }
465
466         //
467         // PRIVATE METHODS
468         //
469
470         /**
471          * Generates an identifier for the given function.
472          * 
473          * @param function
474          *            The name of the function
475          * @return An identifier
476          */
477         private String generateIdentifier(String function) {
478                 return "jFCPlib-" + function + "-" + System.currentTimeMillis();
479         }
480
481         /**
482          * Disconnects the client from the node, handing the given Throwable to
483          * {@link #fireClientDisconnected(Throwable)}.
484          * 
485          * @param throwable
486          *            The exception that caused the disconnect, or <code>null</code>
487          *            if there was no exception
488          */
489         private void disconnect(Throwable throwable) {
490                 fcpConnection.close();
491                 fireClientDisconnected(throwable);
492         }
493
494         /**
495          * FCP listener for {@link HighLevelClient}.
496          * 
497          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
498          * @version $Id$
499          */
500         private class HighLevelClientFcpListener implements FcpListener {
501
502                 /** Mapping from directory to written file (for cleanup). */
503                 private final Map<DirectDiskAccessResult, String> writtenFiles = new HashMap<DirectDiskAccessResult, String>();
504
505                 /**
506                  * Creates a new FCP listener for {@link HighLevelClient}.
507                  */
508                 HighLevelClientFcpListener() {
509                         /* do nothing. */
510                 }
511
512                 //
513                 // PRIVATE METHODS
514                 //
515
516                 /**
517                  * Searches all callback collections for a callback with the given
518                  * identifier and cancels it.
519                  * 
520                  * @param identifier
521                  *            The identifier to search for, or <code>null</code> to
522                  *            cancel all pending requests
523                  */
524                 @SuppressWarnings("synthetic-access")
525                 private void cancelIdentifier(String identifier) {
526                         synchronized (syncObject) {
527                                 if (connectCallback != null) {
528                                         connectCallback.getIntermediaryResult().setFailed(true);
529                                         connectCallback.setDone();
530                                         connectCallback = null;
531                                 }
532                                 if (requestListCallback != null) {
533                                         requestListCallback.getIntermediaryResult().setFailed(true);
534                                         requestListCallback.setDone();
535                                         requestListCallback = null;
536                                 }
537                         }
538                         if (identifier == null) {
539                                 /* key generation callbacks */
540                                 for (Entry<String, HighLevelCallback<KeyGenerationResult>> keyGenerationEntry: keyGenerationCallbacks.entrySet()) {
541                                         keyGenerationEntry.getValue().getIntermediaryResult().setFailed(true);
542                                         keyGenerationEntry.getValue().setDone();
543                                 }
544                                 keyGenerationCallbacks.clear();
545                                 /* peer list callbacks. */
546                                 for (Entry<String, HighLevelCallback<PeerListResult>> peerListEntry: peerListCallbacks.entrySet()) {
547                                         peerListEntry.getValue().getIntermediaryResult().setFailed(true);
548                                         peerListEntry.getValue().setDone();
549                                 }
550                                 peerListCallbacks.clear();
551                                 /* peer callbacks. */
552                                 for (Entry<String, HighLevelCallback<PeerResult>> peerEntry: peerCallbacks.entrySet()) {
553                                         peerEntry.getValue().getIntermediaryResult().setFailed(true);
554                                         peerEntry.getValue().setDone();
555                                 }
556                                 peerCallbacks.clear();
557                                 /* direct disk access callbacks. */
558                                 for (Entry<String, HighLevelCallback<DirectDiskAccessResult>> directDiskAccessEntry: directDiskAccessCallbacks.entrySet()) {
559                                         directDiskAccessEntry.getValue().getIntermediaryResult().setFailed(true);
560                                         directDiskAccessEntry.getValue().setDone();
561                                 }
562                                 directDiskAccessCallbacks.clear();
563                                 /* download callbacks. */
564                                 for (Entry<String, HighLevelProgressCallback<DownloadResult>> downloadEntry: downloadCallbacks.entrySet()) {
565                                         downloadEntry.getValue().getIntermediaryResult().setFailed(true);
566                                         downloadEntry.getValue().setDone();
567                                 }
568                                 downloadCallbacks.clear();
569                         } else {
570                                 HighLevelCallback<KeyGenerationResult> keyGenerationCallback = keyGenerationCallbacks.remove(identifier);
571                                 if (keyGenerationCallback != null) {
572                                         keyGenerationCallback.getIntermediaryResult().setFailed(true);
573                                         keyGenerationCallback.setDone();
574                                         return;
575                                 }
576                                 HighLevelCallback<PeerListResult> peerListCallback = peerListCallbacks.remove(identifier);
577                                 if (peerListCallback != null) {
578                                         peerListCallback.getIntermediaryResult().setFailed(true);
579                                         peerListCallback.setDone();
580                                         return;
581                                 }
582                                 HighLevelCallback<PeerResult> peerCallback = peerCallbacks.remove(identifier);
583                                 if (peerCallback != null) {
584                                         peerCallback.getIntermediaryResult().setFailed(true);
585                                         peerCallback.setDone();
586                                         return;
587                                 }
588                                 HighLevelCallback<DirectDiskAccessResult> directDiskAccessCallback = directDiskAccessCallbacks.remove(identifier);
589                                 if (directDiskAccessCallback != null) {
590                                         directDiskAccessCallback.getIntermediaryResult().setFailed(true);
591                                         directDiskAccessCallback.setDone();
592                                         return;
593                                 }
594                                 HighLevelProgressCallback<DownloadResult> downloadCallback = downloadCallbacks.remove(identifier);
595                                 if (downloadCallback != null) {
596                                         downloadCallback.getIntermediaryResult().setFailed(true);
597                                         downloadCallback.setDone();
598                                         return;
599                                 }
600                         }
601                 }
602
603                 /**
604                  * Reads the given file and returns the first line of the file.
605                  * 
606                  * @param readFilename
607                  *            The name of the file to read
608                  * @return The content of the file
609                  */
610                 private String readContent(String readFilename) {
611                         FileReader fileReader = null;
612                         BufferedReader bufferedFileReader = null;
613                         try {
614                                 fileReader = new FileReader(readFilename);
615                                 bufferedFileReader = new BufferedReader(fileReader);
616                                 String content = bufferedFileReader.readLine();
617                                 return content;
618                         } catch (IOException ioe1) {
619                                 /* swallow. */
620                         } finally {
621                                 FcpUtils.close(bufferedFileReader);
622                                 FcpUtils.close(fileReader);
623                         }
624                         return null;
625                 }
626
627                 /**
628                  * Writes the given content to the given file.
629                  * 
630                  * @param directDiskAccessResult
631                  *            The DDA result
632                  * @param writeFilename
633                  *            The name of the file to write to
634                  * @param writeContent
635                  *            The content to write to the file
636                  */
637                 private void writeContent(DirectDiskAccessResult directDiskAccessResult, String writeFilename, String writeContent) {
638                         if ((writeFilename == null) || (writeContent == null)) {
639                                 return;
640                         }
641                         writtenFiles.put(directDiskAccessResult, writeFilename);
642                         FileWriter fileWriter = null;
643                         try {
644                                 fileWriter = new FileWriter(writeFilename);
645                                 fileWriter.write(writeContent);
646                         } catch (IOException ioe1) {
647                                 /* swallow. */
648                         } finally {
649                                 FcpUtils.close(fileWriter);
650                         }
651                 }
652
653                 /**
654                  * Cleans up any files that written for the given result.
655                  * 
656                  * @param directDiskAccessResult
657                  *            The direct disk access result
658                  */
659                 @SuppressWarnings("synthetic-access")
660                 private void cleanFiles(DirectDiskAccessResult directDiskAccessResult) {
661                         String writeFilename = writtenFiles.remove(directDiskAccessResult);
662                         if (writeFilename != null) {
663                                 if (!new File(writeFilename).delete()) {
664                                         logger.warning("could not delete " + writeFilename);
665                                 }
666                         }
667                 }
668
669                 //
670                 // INTERFACE FcpListener
671                 //
672
673                 /**
674                  * @see net.pterodactylus.fcp.FcpListener#connectionClosed(net.pterodactylus.fcp.FcpConnection,
675                  *      Throwable)
676                  */
677                 @SuppressWarnings("synthetic-access")
678                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
679                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
680                                 return;
681                         }
682                         cancelIdentifier(null);
683                         disconnect(throwable);
684                 }
685
686                 /**
687                  * @see net.pterodactylus.fcp.FcpListener#receivedAllData(net.pterodactylus.fcp.FcpConnection,
688                  *      net.pterodactylus.fcp.AllData)
689                  */
690                 public void receivedAllData(FcpConnection fcpConnection, AllData allData) {
691                         /* TODO */
692                 }
693
694                 /**
695                  * @see net.pterodactylus.fcp.FcpListener#receivedCloseConnectionDuplicateClientName(net.pterodactylus.fcp.FcpConnection,
696                  *      net.pterodactylus.fcp.CloseConnectionDuplicateClientName)
697                  */
698                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
699                         /* TODO */
700                 }
701
702                 /**
703                  * @see net.pterodactylus.fcp.FcpListener#receivedConfigData(net.pterodactylus.fcp.FcpConnection,
704                  *      net.pterodactylus.fcp.ConfigData)
705                  */
706                 public void receivedConfigData(FcpConnection fcpConnection, ConfigData configData) {
707                         /* TODO */
708                 }
709
710                 /**
711                  * @see net.pterodactylus.fcp.FcpListener#receivedDataFound(net.pterodactylus.fcp.FcpConnection,
712                  *      net.pterodactylus.fcp.DataFound)
713                  */
714                 public void receivedDataFound(FcpConnection fcpConnection, DataFound dataFound) {
715                         /* TODO */
716                 }
717
718                 /**
719                  * @see net.pterodactylus.fcp.FcpListener#receivedEndListPeerNotes(net.pterodactylus.fcp.FcpConnection,
720                  *      net.pterodactylus.fcp.EndListPeerNotes)
721                  */
722                 public void receivedEndListPeerNotes(FcpConnection fcpConnection, EndListPeerNotes endListPeerNotes) {
723                         /* TODO */
724                 }
725
726                 /**
727                  * @see net.pterodactylus.fcp.FcpListener#receivedEndListPeers(net.pterodactylus.fcp.FcpConnection,
728                  *      net.pterodactylus.fcp.EndListPeers)
729                  */
730                 @SuppressWarnings("synthetic-access")
731                 public void receivedEndListPeers(FcpConnection fcpConnection, EndListPeers endListPeers) {
732                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
733                                 return;
734                         }
735                         String identifier = endListPeers.getIdentifier();
736                         HighLevelCallback<PeerListResult> peerListCallback = peerListCallbacks.remove(identifier);
737                         if (peerListCallback == null) {
738                                 return;
739                         }
740                         peerListCallback.setDone();
741                 }
742
743                 /**
744                  * @see net.pterodactylus.fcp.FcpListener#receivedEndListPersistentRequests(net.pterodactylus.fcp.FcpConnection,
745                  *      net.pterodactylus.fcp.EndListPersistentRequests)
746                  */
747                 @SuppressWarnings("synthetic-access")
748                 public void receivedEndListPersistentRequests(FcpConnection fcpConnection, EndListPersistentRequests endListPersistentRequests) {
749                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
750                                 return;
751                         }
752                         synchronized (syncObject) {
753                                 if (HighLevelClient.this.requestListCallback == null) {
754                                         logger.log(Level.WARNING, "got EndListPersistentRequests without running request!");
755                                         return;
756                                 }
757                                 requestListCallback.setDone();
758                                 requestListCallback = null;
759                         }
760                 }
761
762                 /**
763                  * @see net.pterodactylus.fcp.FcpListener#receivedFCPPluginReply(net.pterodactylus.fcp.FcpConnection,
764                  *      net.pterodactylus.fcp.FCPPluginReply)
765                  */
766                 public void receivedFCPPluginReply(FcpConnection fcpConnection, FCPPluginReply fcpPluginReply) {
767                         /* TODO */
768                 }
769
770                 /**
771                  * @see net.pterodactylus.fcp.FcpListener#receivedGetFailed(net.pterodactylus.fcp.FcpConnection,
772                  *      net.pterodactylus.fcp.GetFailed)
773                  */
774                 @SuppressWarnings("synthetic-access")
775                 public void receivedGetFailed(FcpConnection fcpConnection, GetFailed getFailed) {
776                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
777                                 return;
778                         }
779                         String identifier = getFailed.getIdentifier();
780                         HighLevelProgressCallback<DownloadResult> downloadCallback = downloadCallbacks.remove(identifier);
781                         if (downloadCallback != null) {
782                                 downloadCallback.getIntermediaryResult().setFailed(true);
783                                 downloadCallback.setDone();
784                                 return;
785                         }
786                         /* unknown identifier? */
787                         logger.warning("unknown identifier for GetFailed: " + identifier);
788                 }
789
790                 /**
791                  * @see net.pterodactylus.fcp.FcpListener#receivedIdentifierCollision(net.pterodactylus.fcp.FcpConnection,
792                  *      net.pterodactylus.fcp.IdentifierCollision)
793                  */
794                 public void receivedIdentifierCollision(FcpConnection fcpConnection, IdentifierCollision identifierCollision) {
795                         /* TODO */
796                 }
797
798                 /**
799                  * @see net.pterodactylus.fcp.FcpListener#receivedMessage(net.pterodactylus.fcp.FcpConnection,
800                  *      net.pterodactylus.fcp.FcpMessage)
801                  */
802                 public void receivedMessage(FcpConnection fcpConnection, FcpMessage fcpMessage) {
803                         /* TODO */
804                 }
805
806                 /**
807                  * @see net.pterodactylus.fcp.FcpListener#receivedNodeData(net.pterodactylus.fcp.FcpConnection,
808                  *      net.pterodactylus.fcp.NodeData)
809                  */
810                 public void receivedNodeData(FcpConnection fcpConnection, NodeData nodeData) {
811                         /* TODO */
812                 }
813
814                 /**
815                  * @see net.pterodactylus.fcp.FcpListener#receivedNodeHello(net.pterodactylus.fcp.FcpConnection,
816                  *      net.pterodactylus.fcp.NodeHello)
817                  */
818                 @SuppressWarnings("synthetic-access")
819                 public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
820                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
821                                 return;
822                         }
823                         synchronized (syncObject) {
824                                 connectCallback.getIntermediaryResult().setFailed(false);
825                                 connectCallback.setDone();
826                                 connectCallback = null;
827                         }
828                         fireClientConnected();
829                 }
830
831                 /**
832                  * @see net.pterodactylus.fcp.FcpListener#receivedPeer(net.pterodactylus.fcp.FcpConnection,
833                  *      net.pterodactylus.fcp.Peer)
834                  */
835                 @SuppressWarnings("synthetic-access")
836                 public void receivedPeer(FcpConnection fcpConnection, Peer peer) {
837                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
838                                 return;
839                         }
840                         String identifier = peer.getIdentifier();
841                         if (identifier == null) {
842                                 return;
843                         }
844                         HighLevelCallback<PeerListResult> peerListCallback = peerListCallbacks.get(identifier);
845                         if (peerListCallback != null) {
846                                 peerListCallback.getIntermediaryResult().addPeer(peer);
847                                 return;
848                         }
849                         HighLevelCallback<PeerResult> peerResult = peerCallbacks.remove(identifier);
850                         if (peerResult != null) {
851                                 peerResult.getIntermediaryResult().setPeer(peer);
852                                 peerResult.setDone();
853                                 return;
854                         }
855                         logger.warning("got Peer message with unknown identifier: " + identifier);
856                 }
857
858                 /**
859                  * @see net.pterodactylus.fcp.FcpListener#receivedPeerNote(net.pterodactylus.fcp.FcpConnection,
860                  *      net.pterodactylus.fcp.PeerNote)
861                  */
862                 public void receivedPeerNote(FcpConnection fcpConnection, PeerNote peerNote) {
863                         /* TODO */
864                 }
865
866                 /**
867                  * @see net.pterodactylus.fcp.FcpListener#receivedPeerRemoved(net.pterodactylus.fcp.FcpConnection,
868                  *      net.pterodactylus.fcp.PeerRemoved)
869                  */
870                 public void receivedPeerRemoved(FcpConnection fcpConnection, PeerRemoved peerRemoved) {
871                         /* TODO */
872                 }
873
874                 /**
875                  * @see net.pterodactylus.fcp.FcpListener#receivedPersistentGet(net.pterodactylus.fcp.FcpConnection,
876                  *      net.pterodactylus.fcp.PersistentGet)
877                  */
878                 @SuppressWarnings("synthetic-access")
879                 public void receivedPersistentGet(FcpConnection fcpConnection, PersistentGet persistentGet) {
880                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
881                                 return;
882                         }
883                         synchronized (syncObject) {
884                                 if (requestListCallback != null) {
885                                         RequestListResult requestListResult = requestListCallback.getIntermediaryResult();
886                                         requestListResult.addRequestResult(new GetRequestResult(persistentGet));
887                                         return;
888                                 }
889                         }
890                         String identifier = persistentGet.getIdentifier();
891                         if (downloadCallbacks.containsKey(identifier)) {
892                                 /* TODO */
893                                 return;
894                         }
895                 }
896
897                 /**
898                  * @see net.pterodactylus.fcp.FcpListener#receivedPersistentPut(net.pterodactylus.fcp.FcpConnection,
899                  *      net.pterodactylus.fcp.PersistentPut)
900                  */
901                 @SuppressWarnings("synthetic-access")
902                 public void receivedPersistentPut(FcpConnection fcpConnection, PersistentPut persistentPut) {
903                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
904                                 return;
905                         }
906                         synchronized (syncObject) {
907                                 if (requestListCallback != null) {
908                                         RequestListResult requestListResult = requestListCallback.getIntermediaryResult();
909                                         requestListResult.addRequestResult(new PutRequestResult(persistentPut));
910                                         return;
911                                 }
912                         }
913                 }
914
915                 /**
916                  * @see net.pterodactylus.fcp.FcpListener#receivedPersistentPutDir(net.pterodactylus.fcp.FcpConnection,
917                  *      net.pterodactylus.fcp.PersistentPutDir)
918                  */
919                 @SuppressWarnings("synthetic-access")
920                 public void receivedPersistentPutDir(FcpConnection fcpConnection, PersistentPutDir persistentPutDir) {
921                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
922                                 return;
923                         }
924                         synchronized (syncObject) {
925                                 if (requestListCallback != null) {
926                                         RequestListResult requestListResult = requestListCallback.getIntermediaryResult();
927                                         requestListResult.addRequestResult(new PutDirRequestResult(persistentPutDir));
928                                         return;
929                                 }
930                         }
931                 }
932
933                 /**
934                  * @see net.pterodactylus.fcp.FcpListener#receivedPersistentRequestModified(net.pterodactylus.fcp.FcpConnection,
935                  *      net.pterodactylus.fcp.PersistentRequestModified)
936                  */
937                 public void receivedPersistentRequestModified(FcpConnection fcpConnection, PersistentRequestModified persistentRequestModified) {
938                         /* TODO */
939                 }
940
941                 /**
942                  * @see net.pterodactylus.fcp.FcpListener#receivedPersistentRequestRemoved(net.pterodactylus.fcp.FcpConnection,
943                  *      net.pterodactylus.fcp.PersistentRequestRemoved)
944                  */
945                 public void receivedPersistentRequestRemoved(FcpConnection fcpConnection, PersistentRequestRemoved persistentRequestRemoved) {
946                         /* TODO */
947                 }
948
949                 /**
950                  * @see net.pterodactylus.fcp.FcpListener#receivedPluginInfo(net.pterodactylus.fcp.FcpConnection,
951                  *      net.pterodactylus.fcp.PluginInfo)
952                  */
953                 public void receivedPluginInfo(FcpConnection fcpConnection, PluginInfo pluginInfo) {
954                         /* TODO */
955                 }
956
957                 /**
958                  * @see net.pterodactylus.fcp.FcpListener#receivedProtocolError(net.pterodactylus.fcp.FcpConnection,
959                  *      net.pterodactylus.fcp.ProtocolError)
960                  */
961                 @SuppressWarnings("synthetic-access")
962                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
963                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
964                                 return;
965                         }
966                         String identifier = protocolError.getIdentifier();
967                         if (identifier == null) {
968                                 return;
969                         }
970                         cancelIdentifier(identifier);
971                 }
972
973                 /**
974                  * @see net.pterodactylus.fcp.FcpListener#receivedPutFailed(net.pterodactylus.fcp.FcpConnection,
975                  *      net.pterodactylus.fcp.PutFailed)
976                  */
977                 public void receivedPutFailed(FcpConnection fcpConnection, PutFailed putFailed) {
978                         /* TODO */
979                 }
980
981                 /**
982                  * @see net.pterodactylus.fcp.FcpListener#receivedPutFetchable(net.pterodactylus.fcp.FcpConnection,
983                  *      net.pterodactylus.fcp.PutFetchable)
984                  */
985                 public void receivedPutFetchable(FcpConnection fcpConnection, PutFetchable putFetchable) {
986                         /* TODO */
987                 }
988
989                 /**
990                  * @see net.pterodactylus.fcp.FcpListener#receivedPutSuccessful(net.pterodactylus.fcp.FcpConnection,
991                  *      net.pterodactylus.fcp.PutSuccessful)
992                  */
993                 public void receivedPutSuccessful(FcpConnection fcpConnection, PutSuccessful putSuccessful) {
994                         /* TODO */
995                 }
996
997                 /**
998                  * @see net.pterodactylus.fcp.FcpListener#receivedSSKKeypair(net.pterodactylus.fcp.FcpConnection,
999                  *      net.pterodactylus.fcp.SSKKeypair)
1000                  */
1001                 @SuppressWarnings("synthetic-access")
1002                 public void receivedSSKKeypair(FcpConnection fcpConnection, SSKKeypair sskKeypair) {
1003                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
1004                                 return;
1005                         }
1006                         HighLevelCallback<KeyGenerationResult> keyGenerationCallback = keyGenerationCallbacks.remove(sskKeypair.getIdentifier());
1007                         if (keyGenerationCallback == null) {
1008                                 return;
1009                         }
1010                         KeyGenerationResult keyGenerationResult = keyGenerationCallback.getIntermediaryResult();
1011                         keyGenerationResult.setInsertURI(sskKeypair.getInsertURI());
1012                         keyGenerationResult.setRequestURI(sskKeypair.getRequestURI());
1013                         keyGenerationCallback.setDone();
1014                 }
1015
1016                 /**
1017                  * @see net.pterodactylus.fcp.FcpListener#receivedSimpleProgress(net.pterodactylus.fcp.FcpConnection,
1018                  *      net.pterodactylus.fcp.SimpleProgress)
1019                  */
1020                 @SuppressWarnings("synthetic-access")
1021                 public void receivedSimpleProgress(FcpConnection fcpConnection, SimpleProgress simpleProgress) {
1022                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
1023                                 return;
1024                         }
1025                         String identifier = simpleProgress.getIdentifier();
1026                         HighLevelProgressCallback<DownloadResult> downloadCallback = downloadCallbacks.get(identifier);
1027                         if (downloadCallback != null) {
1028                                 DownloadResult downloadResult = downloadCallback.getIntermediaryResult();
1029                                 downloadResult.setTotalBlocks(simpleProgress.getTotal());
1030                                 downloadResult.setRequiredBlocks(simpleProgress.getRequired());
1031                                 downloadResult.setSuccessfulBlocks(simpleProgress.getSucceeded());
1032                                 downloadResult.setFailedBlocks(simpleProgress.getFailed());
1033                                 downloadResult.setFatallyFailedBlocks(simpleProgress.getFatallyFailed());
1034                                 downloadResult.setTotalFinalized(simpleProgress.isFinalizedTotal());
1035                                 downloadCallback.progressUpdated();
1036                                 return;
1037                         }
1038                         /* unknown identifier? */
1039                         logger.warning("unknown identifier for SimpleProgress: " + identifier);
1040                 }
1041
1042                 /**
1043                  * @see net.pterodactylus.fcp.FcpListener#receivedStartedCompression(net.pterodactylus.fcp.FcpConnection,
1044                  *      net.pterodactylus.fcp.StartedCompression)
1045                  */
1046                 public void receivedStartedCompression(FcpConnection fcpConnection, StartedCompression startedCompression) {
1047                         /* TODO */
1048                 }
1049
1050                 /**
1051                  * @see net.pterodactylus.fcp.FcpListener#receivedSubscribedUSKUpdate(net.pterodactylus.fcp.FcpConnection,
1052                  *      net.pterodactylus.fcp.SubscribedUSKUpdate)
1053                  */
1054                 public void receivedSubscribedUSKUpdate(FcpConnection fcpConnection, SubscribedUSKUpdate subscribedUSKUpdate) {
1055                         /* TODO */
1056                 }
1057
1058                 /**
1059                  * @see net.pterodactylus.fcp.FcpListener#receivedTestDDAComplete(net.pterodactylus.fcp.FcpConnection,
1060                  *      net.pterodactylus.fcp.TestDDAComplete)
1061                  */
1062                 @SuppressWarnings("synthetic-access")
1063                 public void receivedTestDDAComplete(FcpConnection fcpConnection, TestDDAComplete testDDAComplete) {
1064                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
1065                                 return;
1066                         }
1067                         String directory = testDDAComplete.getDirectory();
1068                         if (directory == null) {
1069                                 return;
1070                         }
1071                         HighLevelCallback<DirectDiskAccessResult> directDiskAccessCallback = directDiskAccessCallbacks.remove(directory);
1072                         DirectDiskAccessResult directDiskAccessResult = directDiskAccessCallback.getIntermediaryResult();
1073                         cleanFiles(directDiskAccessResult);
1074                         directDiskAccessResult.setReadAllowed(testDDAComplete.isReadDirectoryAllowed());
1075                         directDiskAccessResult.setWriteAllowed(testDDAComplete.isWriteDirectoryAllowed());
1076                         directDiskAccessCallback.setDone();
1077                 }
1078
1079                 /**
1080                  * @see net.pterodactylus.fcp.FcpListener#receivedTestDDAReply(net.pterodactylus.fcp.FcpConnection,
1081                  *      net.pterodactylus.fcp.TestDDAReply)
1082                  */
1083                 @SuppressWarnings("synthetic-access")
1084                 public void receivedTestDDAReply(FcpConnection fcpConnection, TestDDAReply testDDAReply) {
1085                         if (fcpConnection != HighLevelClient.this.fcpConnection) {
1086                                 return;
1087                         }
1088                         String directory = testDDAReply.getDirectory();
1089                         if (directory == null) {
1090                                 return;
1091                         }
1092                         DirectDiskAccessResult directDiskAccessResult = directDiskAccessCallbacks.get(directory).getIntermediaryResult();
1093                         String readFilename = testDDAReply.getReadFilename();
1094                         String readContent = readContent(readFilename);
1095                         String writeFilename = testDDAReply.getWriteFilename();
1096                         String writeContent = testDDAReply.getContentToWrite();
1097                         writeContent(directDiskAccessResult, writeFilename, writeContent);
1098                         TestDDAResponse testDDAResponse = new TestDDAResponse(directory, readContent);
1099                         try {
1100                                 fcpConnection.sendMessage(testDDAResponse);
1101                         } catch (IOException e) {
1102                                 /* swallow. I’m verry unhappy about this. */
1103                         }
1104                 }
1105
1106                 /**
1107                  * @see net.pterodactylus.fcp.FcpListener#receivedURIGenerated(net.pterodactylus.fcp.FcpConnection,
1108                  *      net.pterodactylus.fcp.URIGenerated)
1109                  */
1110                 public void receivedURIGenerated(FcpConnection fcpConnection, URIGenerated uriGenerated) {
1111                         /* TODO */
1112                 }
1113
1114                 /**
1115                  * @see net.pterodactylus.fcp.FcpListener#receivedUnknownNodeIdentifier(net.pterodactylus.fcp.FcpConnection,
1116                  *      net.pterodactylus.fcp.UnknownNodeIdentifier)
1117                  */
1118                 public void receivedUnknownNodeIdentifier(FcpConnection fcpConnection, UnknownNodeIdentifier unknownNodeIdentifier) {
1119                         /* TODO */
1120                 }
1121
1122                 /**
1123                  * @see net.pterodactylus.fcp.FcpListener#receivedUnknownPeerNoteType(net.pterodactylus.fcp.FcpConnection,
1124                  *      net.pterodactylus.fcp.UnknownPeerNoteType)
1125                  */
1126                 public void receivedUnknownPeerNoteType(FcpConnection fcpConnection, UnknownPeerNoteType unknownPeerNoteType) {
1127                         /* TODO */
1128                 }
1129
1130                 /**
1131                  * @see net.pterodactylus.fcp.FcpListener#receviedFinishedCompression(net.pterodactylus.fcp.FcpConnection,
1132                  *      net.pterodactylus.fcp.FinishedCompression)
1133                  */
1134                 public void receviedFinishedCompression(FcpConnection fcpConnection, FinishedCompression finishedCompression) {
1135                         /* TODO */
1136                 }
1137
1138         }
1139
1140 }