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