import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.logging.Level;
import java.util.logging.Logger;
import net.pterodactylus.fcp.AddPeer;
import net.pterodactylus.fcp.GetFailed;
import net.pterodactylus.fcp.IdentifierCollision;
import net.pterodactylus.fcp.ListPeers;
+import net.pterodactylus.fcp.ListPersistentRequests;
import net.pterodactylus.fcp.NodeData;
import net.pterodactylus.fcp.NodeHello;
import net.pterodactylus.fcp.NodeRef;
/**
* A high-level client that allows simple yet full-featured access to a Freenet
* node.
- *
+ *
* @author David ‘Bombe’ Roden <bombe@freenetproject.org>
* @version $Id$
*/
/** The FCP connection to the node. */
private FcpConnection fcpConnection;
+ /** Listeners for high-level client events. */
+ private List<HighLevelClientListener> highLevelClientListeners = Collections.synchronizedList(new ArrayList<HighLevelClientListener>());
+
/** The listener for the connection. */
private HighLevelClientFcpListener highLevelClientFcpListener = new HighLevelClientFcpListener();
/** Mapping from request identifiers to download callbacks. */
private Map<String, HighLevelProgressCallback<DownloadResult>> downloadCallbacks = Collections.synchronizedMap(new HashMap<String, HighLevelProgressCallback<DownloadResult>>());
+ /** The callback for {@link #getRequests()}. */
+ private HighLevelCallback<RequestListResult> requestListCallback;
+
/**
* Creates a new high-level client that connects to a node on
* <code>localhost</code>.
- *
+ *
* @param clientName
* The name of the client
* @throws UnknownHostException
/**
* Creates a new high-level client that connects to a node on the given
* host.
- *
+ *
* @param clientName
* The name of the client
* @param host
/**
* Creates a new high-level client that connects to a node on the given
* host.
- *
+ *
* @param clientName
* The name of the client
* @param host
/**
* Creates a new high-level client that connects to a node at the given
* address.
- *
+ *
* @param clientName
* The name of the client
* @param address
}
//
+ // EVENT MANAGEMENT
+ //
+
+ /**
+ * Adds the given high-level client listener to list of listeners.
+ *
+ * @param highLevelClientListener
+ * The listener to add
+ */
+ public void addHighLevelClientListener(HighLevelClientListener highLevelClientListener) {
+ highLevelClientListeners.add(highLevelClientListener);
+ }
+
+ /**
+ * Removes the given high-level client listener from the list of listeners.
+ *
+ * @param highLevelClientListener
+ * The listener to remove
+ */
+ public void removeHighLevelClientListener(HighLevelClientListener highLevelClientListener) {
+ highLevelClientListeners.remove(highLevelClientListener);
+ }
+
+ /**
+ * Notifies all listeners that a client has connected.
+ */
+ private void fireClientConnected() {
+ for (HighLevelClientListener highLevelClientListener: highLevelClientListeners) {
+ highLevelClientListener.clientConnected(this);
+ }
+ }
+
+ /**
+ * Notifies all listeners that a client has disconnected.
+ *
+ * @param throwable
+ * The exception that caused the disconnect, or <code>null</code>
+ * if there was no exception
+ */
+ private void fireClientDisconnected(Throwable throwable) {
+ for (HighLevelClientListener highLevelClientListener: highLevelClientListeners) {
+ highLevelClientListener.clientDisconnected(this, throwable);
+ }
+ }
+
+ //
// ACCESSORS
//
+ /**
+ * Returns the FCP connection that backs this high-level client. This method
+ * should be used with care as fiddling around with the FCP connection can
+ * easily break the high-level client if you don’t know what you’re doing!
+ *
+ * @return The FCP connection of this client
+ */
+ public FcpConnection getFcpConnection() {
+ return fcpConnection;
+ }
+
//
// ACTIONS
//
/**
* Connects the client.
- *
+ *
* @return A callback with a connection result
* @throws IOException
* if an I/O error occurs communicating with the node
public HighLevelCallback<ConnectResult> connect() throws IOException {
fcpConnection = new FcpConnection(address, port);
fcpConnection.addFcpListener(highLevelClientFcpListener);
+ fcpConnection.connect();
ClientHello clientHello = new ClientHello(clientName);
connectCallback = new HighLevelCallback<ConnectResult>(new ConnectResult());
fcpConnection.sendMessage(clientHello);
* Disconnects the client from the node.
*/
public void disconnect() {
- fcpConnection.disconnect();
+ disconnect(null);
}
/**
* Generates a new SSK keypair.
- *
+ *
* @return A callback with the keypair
* @throws IOException
* if an I/O error occurs communicating with the node
/**
* Gets a list of all peers from the node.
- *
+ *
* @return A callback with the peer list
* @throws IOException
* if an I/O error occurs with the node
/**
* Adds the peer whose noderef is stored in the given file.
- *
+ *
* @param nodeRefFile
* The name of the file the peer’s noderef is stored in
* @return A peer callback
/**
* Adds the peer whose noderef is stored in the given file.
- *
+ *
* @param nodeRefURL
* The URL where the peer’s noderef is stored
* @return A peer callback
/**
* Adds the peer whose noderef is stored in the given file.
- *
+ *
* @param nodeRef
* The peer’s noderef
* @return A peer callback
* Checks whether direct disk access for the given directory is possible.
* You have to perform this check before you can upload or download anything
* from or the disk directly!
- *
+ *
* @param directory
* The directory to check
* @param wantRead
* Starts a download. Files can either be download to disk or streamed from
* the node. When downloading to disk you have to perform a direct disk
* access check for the directory you want to put the downloaded file in!
- *
+ *
* @see #checkDirectDiskAccess(String, boolean, boolean)
* @param uri
* The URI to get
return downloadCallback;
}
+ /**
+ * Requests a list of all running requests from the node.
+ *
+ * @return The request list result
+ * @throws IOException
+ * if an I/O errors communicating with the node
+ */
+ public HighLevelCallback<RequestListResult> getRequests() throws IOException {
+ String identifier = generateIdentifier("list-persistent-requests");
+ ListPersistentRequests listPersistentRequests = new ListPersistentRequests();
+ synchronized (syncObject) {
+ if (requestListCallback != null) {
+ logger.log(Level.SEVERE, "getRequests() called with request still running!");
+ }
+ requestListCallback = new HighLevelCallback<RequestListResult>(new RequestListResult(identifier));
+ }
+ fcpConnection.sendMessage(listPersistentRequests);
+ return requestListCallback;
+ }
+
//
// PRIVATE METHODS
//
/**
* Generates an identifier for the given function.
- *
+ *
* @param function
* The name of the function
* @return An identifier
}
/**
+ * Disconnects the client from the node, handing the given Throwable to
+ * {@link #fireClientDisconnected(Throwable)}.
+ *
+ * @param throwable
+ * The exception that caused the disconnect, or <code>null</code>
+ * if there was no exception
+ */
+ private void disconnect(Throwable throwable) {
+ fcpConnection.close();
+ fireClientDisconnected(throwable);
+ }
+
+ /**
* FCP listener for {@link HighLevelClient}.
- *
+ *
* @author David ‘Bombe’ Roden <bombe@freenetproject.org>
* @version $Id$
*/
/**
* Searches all callback collections for a callback with the given
* identifier and cancels it.
- *
+ *
* @param identifier
* The identifier to search for, or <code>null</code> to
* cancel all pending requests
connectCallback.setDone();
connectCallback = null;
}
+ if (requestListCallback != null) {
+ requestListCallback.getIntermediaryResult().setFailed(true);
+ requestListCallback.setDone();
+ requestListCallback = null;
+ }
}
if (identifier == null) {
/* key generation callbacks */
/**
* Reads the given file and returns the first line of the file.
- *
+ *
* @param readFilename
* The name of the file to read
* @return The content of the file
/**
* Writes the given content to the given file.
- *
+ *
* @param directDiskAccessResult
* The DDA result
* @param writeFilename
/**
* Cleans up any files that written for the given result.
- *
+ *
* @param directDiskAccessResult
* The direct disk access result
*/
//
/**
- * @see net.pterodactylus.fcp.FcpListener#connectionClosed(net.pterodactylus.fcp.FcpConnection)
+ * @see net.pterodactylus.fcp.FcpListener#connectionClosed(net.pterodactylus.fcp.FcpConnection,
+ * Throwable)
*/
@SuppressWarnings("synthetic-access")
- public void connectionClosed(FcpConnection fcpConnection) {
+ public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
if (fcpConnection != HighLevelClient.this.fcpConnection) {
return;
}
cancelIdentifier(null);
+ disconnect(throwable);
}
/**
* @see net.pterodactylus.fcp.FcpListener#receivedEndListPersistentRequests(net.pterodactylus.fcp.FcpConnection,
* net.pterodactylus.fcp.EndListPersistentRequests)
*/
+ @SuppressWarnings("synthetic-access")
public void receivedEndListPersistentRequests(FcpConnection fcpConnection, EndListPersistentRequests endListPersistentRequests) {
- /* TODO */
+ if (fcpConnection != HighLevelClient.this.fcpConnection) {
+ return;
+ }
+ synchronized (syncObject) {
+ if (HighLevelClient.this.requestListCallback == null) {
+ logger.log(Level.WARNING, "got EndListPersistentRequests without running request!");
+ return;
+ }
+ requestListCallback.setDone();
+ requestListCallback = null;
+ }
}
/**
connectCallback.setDone();
connectCallback = null;
}
+ fireClientConnected();
}
/**
if (fcpConnection != HighLevelClient.this.fcpConnection) {
return;
}
+ synchronized (syncObject) {
+ if (requestListCallback != null) {
+ RequestListResult requestListResult = requestListCallback.getIntermediaryResult();
+ requestListResult.addRequestResult(new GetRequestResult(persistentGet));
+ return;
+ }
+ }
String identifier = persistentGet.getIdentifier();
if (downloadCallbacks.containsKey(identifier)) {
- /* ignore, because a download does not care about this. */
+ /* TODO */
return;
}
}
* @see net.pterodactylus.fcp.FcpListener#receivedPersistentPut(net.pterodactylus.fcp.FcpConnection,
* net.pterodactylus.fcp.PersistentPut)
*/
+ @SuppressWarnings("synthetic-access")
public void receivedPersistentPut(FcpConnection fcpConnection, PersistentPut persistentPut) {
- /* TODO */
+ if (fcpConnection != HighLevelClient.this.fcpConnection) {
+ return;
+ }
+ synchronized (syncObject) {
+ if (requestListCallback != null) {
+ RequestListResult requestListResult = requestListCallback.getIntermediaryResult();
+ requestListResult.addRequestResult(new PutRequestResult(persistentPut));
+ return;
+ }
+ }
}
/**
* @see net.pterodactylus.fcp.FcpListener#receivedPersistentPutDir(net.pterodactylus.fcp.FcpConnection,
* net.pterodactylus.fcp.PersistentPutDir)
*/
+ @SuppressWarnings("synthetic-access")
public void receivedPersistentPutDir(FcpConnection fcpConnection, PersistentPutDir persistentPutDir) {
- /* TODO */
+ if (fcpConnection != HighLevelClient.this.fcpConnection) {
+ return;
+ }
+ synchronized (syncObject) {
+ if (requestListCallback != null) {
+ RequestListResult requestListResult = requestListCallback.getIntermediaryResult();
+ requestListResult.addRequestResult(new PutDirRequestResult(persistentPutDir));
+ return;
+ }
+ }
}
/**