*
* @param node
* The node to add
+ * @return <code>true</code> if the node was added, <code>false</code>
+ * if it was not added because it was already known
* @throws UnknownHostException
* if the hostname of the node can not be resolved
*/
- public void addNode(Node node) throws UnknownHostException;
+ public boolean addNode(Node node) throws UnknownHostException;
/**
* Removes the given node from the core.
}
/**
+ * Notifies all listeners that a connection to a node has failed.
+ *
+ * @param node
+ * The node that could not be connected
+ * @param cause
+ * The cause of the failure
+ */
+ private void fireNodeConnectionFailed(Node node, Throwable cause) {
+ for (CoreListener coreListener: coreListeners) {
+ coreListener.nodeConnectionFailed(node, cause);
+ }
+ }
+
+ /**
* Notifies all listeners that the given node was disconnected.
*
* @param node
/**
* {@inheritDoc}
*/
- public void addNode(Node node) throws UnknownHostException {
- nodeManager.addNode(node);
- fireNodeAdded(node);
+ public boolean addNode(Node node) throws UnknownHostException {
+ return nodeManager.addNode(node);
}
/**
*/
public void removeNode(Node node) {
nodeManager.removeNode(node);
- fireNodeRemoved(node);
}
/**
/**
* {@inheritDoc}
*/
+ public void nodeConnectionFailed(Node node, Throwable cause) {
+ fireNodeConnectionFailed(node, cause);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public void nodeDisconnected(Node node, Throwable throwable) {
fireNodeDisconnected(node, throwable);
}
* boolean)
*/
public void requestProgressed(Node node, Request request, int totalBlocks, int requiredBlocks, int successfulBlocks, int failedBlocks, int fatallyFailedBlocks, boolean finalizedTotal) {
- fireRequestProgressed(node, request, totalBlocks, requiredBlocks, successfulBlocks, failedBlocks, fatallyFailedBlocks, finalizedTotal);
- }
+ fireRequestProgressed(node, request, totalBlocks, requiredBlocks, successfulBlocks, failedBlocks, fatallyFailedBlocks, finalizedTotal);
+ }
}
public void nodeConnected(Node node);
/**
+ * Notifies a listener that a connection to a node has failed.
+ *
+ * @param node
+ * The node that could not be connected
+ * @param cause
+ * The cause of the failure
+ */
+ public void nodeConnectionFailed(Node node, Throwable cause);
+
+ /**
* Notifies all listeners that the core disconnected from the given node.
*
* @param node
package net.pterodactylus.jsite.core;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* Container for a Freenet node.
- *
+ *
* @author David ‘Bombe’ Roden <bombe@freenetproject.org>
* @version $Id$
*/
public class Node {
+ /** Property change listeners. */
+ private final List<PropertyChangeListener> propertyChangeListeners = Collections.synchronizedList(new ArrayList<PropertyChangeListener>());
+
/** The name of the node. */
private String name;
/** The port number of the node. */
private int port;
+ //
+ // EVENT MANAGEMENT
+ //
+
+ /**
+ * Adds a property change listener.
+ *
+ * @param propertyChangeListener
+ * The property change listener to add
+ */
+ public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
+ propertyChangeListeners.add(propertyChangeListener);
+ }
+
+ /**
+ * Removes a property change listener.
+ *
+ * @param propertyChangeListener
+ * The property change listener to remove
+ */
+ public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) {
+ propertyChangeListeners.remove(propertyChangeListener);
+ }
+
+ /**
+ * Notifies all listeners that a property has changed.
+ *
+ * @param property
+ * The name of the property
+ * @param oldValue
+ * The old value of the property
+ * @param newValue
+ * The new value of the property
+ */
+ private void firePropertyChange(String property, Object oldValue, Object newValue) {
+ PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(this, property, oldValue, newValue);
+ for (PropertyChangeListener propertyChangeListener: propertyChangeListeners) {
+ propertyChangeListener.propertyChange(propertyChangeEvent);
+ }
+
+ }
+
+ //
+ // ACCESSORS
+ //
+
/**
* Returns the user-given name of the node.
- *
+ *
* @return The name of the node
*/
public String getName() {
/**
* Sets the user-given name of the node.
- *
+ *
* @param name
* The name of the node
*/
public void setName(String name) {
+ String oldName = this.name;
this.name = name;
+ if (((oldName != null) && (name == null)) || ((oldName == null) && (name != null)) || ((name != null) && !name.equals(oldName))) {
+ firePropertyChange("name", oldName, name);
+ }
}
/**
* Returns the hostname of the node.
- *
+ *
* @return The hostname of the node
*/
public String getHostname() {
/**
* Sets the hostname of the node.
- *
+ *
* @param hostname
* The hostname of the node
*/
public void setHostname(String hostname) {
+ String oldHostname = this.hostname;
this.hostname = hostname;
+ if (((oldHostname != null) && (hostname == null)) || ((oldHostname == null) && (hostname != null)) || ((hostname != null) && !hostname.equals(oldHostname))) {
+ firePropertyChange("hostname", oldHostname, hostname);
+ }
}
/**
* Returns the port number of the node.
- *
+ *
* @return The port number of the node
*/
public int getPort() {
/**
* Sets the port number of the node.
- *
+ *
* @param port
* The port number of the node
*/
public void setPort(int port) {
+ int oldPort = this.port;
this.port = port;
- }
-
- /**
- * {@inheritDoc} Two Node objects are considered equal if their hostnames
- * and their port numbers are equal.
- */
- @Override
- public boolean equals(Object object) {
- if ((object == null) || !(object instanceof Node)) {
- return false;
+ if (oldPort != port) {
+ firePropertyChange("port", oldPort, port);
}
- Node node = (Node) object;
- return hostname.equals(node.hostname) && port == node.port;
}
+// /**
+// * {@inheritDoc} Two Node objects are considered equal if their hostnames
+// * and their port numbers are equal.
+// */
+// @Override
+// public boolean equals(Object object) {
+// if ((object == null) || !(object instanceof Node)) {
+// return false;
+// }
+// Node node = (Node) object;
+// return hostname.equals(node.hostname) && port == node.port;
+// }
+
+// /**
+// * {@inheritDoc}
+// */
+// @Override
+// public int hashCode() {
+// return hostname.hashCode() ^ (-1 - port);
+// }
+
/**
* {@inheritDoc}
*/
public void nodeConnected(Node node);
/**
- * Notifies a listener that a connection to the given node was severed. The
- * listener is responsible for
+ * Notifies a listener that a connection to a node has failed.
+ *
+ * @param node
+ * The node that could not be connected
+ * @param cause
+ * The cause of the failure
+ */
+ public void nodeConnectionFailed(Node node, Throwable cause);
+
+ /**
+ * Notifies a listener that a connection to the given node was severed. If
+ * the listener needs the high-level client associated with the node for
+ * anything else (like deregistering as listener from it) it should retrieve
+ * the high-level client using {@link NodeManager#getHighLevelClient(Node)}
+ * before this method returns!
*
* @param node
* The node that is now disconnected
package net.pterodactylus.jsite.core;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
-import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
* @author David ‘Bombe’ Roden <bombe@freenetproject.org>
* @version $Id$
*/
-public class NodeManager implements Iterable<Node>, HighLevelClientListener {
+public class NodeManager implements Iterable<Node>, PropertyChangeListener, HighLevelClientListener {
/** Logger. */
private static final Logger logger = Logging.getLogger(NodeManager.class.getName());
/** All FCP connections. */
private Map<Node, HighLevelClient> nodeClients = Collections.synchronizedMap(new HashMap<Node, HighLevelClient>());
- /** Keeps track of which connection is in use right now. */
- private Set<HighLevelClient> usedConnections = Collections.synchronizedSet(new HashSet<HighLevelClient>());
-
/** Maps nodes to high-level clients. */
private Map<HighLevelClient, Node> clientNodes = Collections.synchronizedMap(new HashMap<HighLevelClient, Node>());
}
/**
+ * Notifies all listeners that a connection to a node has failed.
+ *
+ * @param node
+ * The node that could not be connected
+ * @param cause
+ * The cause of the failure
+ */
+ private void fireNodeConnectionFailed(Node node, Throwable cause) {
+ for (NodeListener nodeListener: nodeListeners) {
+ nodeListener.nodeConnectionFailed(node, cause);
+ }
+ }
+
+ /**
* Notifies all listeners that the given node was disconnected.
*
* @param node
logger.fine("loaded " + loadedNodes.size() + " nodes from config");
synchronized (syncObject) {
nodes.clear();
- for (Node node: nodes) {
+ for (Node node: loadedNodes) {
addNode(node);
}
}
* @see #connect(Node)
* @param node
* The node to connect to
- * @throws UnknownHostException
- * if the hostname of the node can not be resolved
+ * @return <code>true</code> if the node was added, <code>false</code>
+ * if the node was not added because it was already known
*/
- public void addNode(Node node) throws UnknownHostException {
- synchronized (syncObject) {
- if (!nodes.contains(node)) {
- HighLevelClient highLevelClient= new HighLevelClient(clientName, node.getHostname(), node.getPort());
- nodes.add(node);
- clientNodes.put(highLevelClient, node);
- nodeClients.put(node, highLevelClient);
- highLevelClient.addHighLevelClientListener(this);
- fireNodeAdded(node);
- }
+ public boolean addNode(Node node) {
+ if (nodes.contains(node)) {
+ logger.warning("was told to add already known node: " + node);
+ return false;
}
+ node.addPropertyChangeListener(this);
+ HighLevelClient highLevelClient = new HighLevelClient(clientName);
+ nodes.add(node);
+ clientNodes.put(highLevelClient, node);
+ nodeClients.put(node, highLevelClient);
+ highLevelClient.addHighLevelClientListener(this);
+ fireNodeAdded(node);
+ return true;
}
/**
if (nodeClients.containsKey(node)) {
disconnect(node);
}
+ node.removePropertyChangeListener(this);
fireNodeRemoved(node);
}
}
*/
public void connect(Node node) {
HighLevelClient highLevelClient;
- synchronized (syncObject) {
- highLevelClient = nodeClients.get(node);
+ highLevelClient = nodeClients.get(node);
+ if (highLevelClient == null) {
+ logger.warning("was told to connect to unknown node: " + node);
+ return;
}
try {
- highLevelClient.connect();
+ highLevelClient.connect(node.getHostname(), node.getPort());
+ } catch (UnknownHostException uhe1) {
+ fireNodeConnectionFailed(node, uhe1);
} catch (IOException ioe1) {
- fireNodeDisconnected(node, ioe1);
+ fireNodeConnectionFailed(node, ioe1);
}
}
}
/**
- * “Borrows” a high-level client for the given node. A borrowed client
- * <strong>has</strong> to be returned to the node manager using
- * {@link #returnHighLevelClient(HighLevelClient)} when it is no longer in
- * use, i.e. after a message has been sent! This method will block until a
- * high-level client for the given node is available.
+ * Returns the high-level client for a given node.
*
* @param node
* The node to get a high-level client for
* @return The high-level client for a node, or <code>null</code> if the
* node was disconnected or removed
*/
- public HighLevelClient borrowHighLevelClient(Node node) {
- synchronized (syncObject) {
- if (!nodeClients.containsKey(node)) {
- return null;
- }
- HighLevelClient highLevelClient = nodeClients.get(node);
- while (nodeClients.containsKey(node) && usedConnections.contains(highLevelClient)) {
- try {
- syncObject.wait();
- } catch (InterruptedException ie1) {
- /* ignore. TODO - check. */
- }
- }
- if (!nodeClients.containsKey(node)) {
- return null;
- }
- usedConnections.add(highLevelClient);
- return highLevelClient;
- }
- }
-
- /**
- * Returns a borrowed high-level client.
- *
- * @see #borrowHighLevelClient(Node)
- * @param highLevelClient
- * The high-level client to return
- */
- public void returnHighLevelClient(HighLevelClient highLevelClient) {
- synchronized (syncObject) {
- if (!clientNodes.containsKey(highLevelClient)) {
- return;
- }
- usedConnections.remove(highLevelClient);
- syncObject.notifyAll();
- }
+ public HighLevelClient getHighLevelClient(Node node) {
+ return nodeClients.get(node);
}
//
// PRIVATE METHODS
//
- /**
- * Finds a currently unused high-level client, optionally waiting until a
- * client is free and marking it used.
- *
- * @param wait
- * <code>true</code> to wait for a free connection,
- * <code>false</code> to return <code>null</code>
- * @param markAsUsed
- * <code>true</code> to mark the connection as used before
- * returning it, <code>false</code> not to mark it
- * @return An unused FCP connection, or <code>null</code> if no connection
- * could be found
- */
- @SuppressWarnings("unused")
- private HighLevelClient findUnusedClient(boolean wait, boolean markAsUsed) {
- synchronized (syncObject) {
- HighLevelClient freeHighLevelClient = null;
- while (freeHighLevelClient == null) {
- for (HighLevelClient highLevelClient: nodeClients.values()) {
- if (!usedConnections.contains(highLevelClient)) {
- freeHighLevelClient = highLevelClient;
- break;
- }
- }
- if (freeHighLevelClient != null) {
- if (markAsUsed) {
- usedConnections.add(freeHighLevelClient);
- }
- return freeHighLevelClient;
- }
- if (!wait) {
- return null;
- }
- try {
- syncObject.wait();
- } catch (InterruptedException e) {
- /* ignore, just re-check. */
- }
- }
- /* we never get here, but the compiler doesn't realize. */
- return null;
- }
- }
-
//
// INTERFACE HighLevelClientListener
//
public void clientDisconnected(HighLevelClient highLevelClient, Throwable throwable) {
logger.log(Level.FINER, "clientDisconnected(c=" + highLevelClient + ",t=" + throwable + ")");
synchronized (syncObject) {
- Node node = clientNodes.remove(highLevelClient);
+ Node node = clientNodes.get(highLevelClient);
if (node == null) {
logger.log(Level.WARNING, "got event for unknown client");
return;
}
- nodeClients.remove(node);
- usedConnections.remove(highLevelClient);
fireNodeDisconnected(node, throwable);
}
}
+ //
+ // INTERFACE PropertyChangeListener
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
+ Object eventSource = propertyChangeEvent.getSource();
+ if (eventSource instanceof Node) {
+ String propertyName = propertyChangeEvent.getPropertyName();
+ if ("hostname".equals(propertyName) || "port".equals(propertyName)) {
+ Node node = (Node) eventSource;
+ HighLevelClient highLevelClient = nodeClients.get(node);
+ if (highLevelClient == null) {
+ logger.log(Level.WARNING, "got property change event for unknown node: " + node);
+ return;
+ }
+ if (highLevelClient.isConnected()) {
+ highLevelClient.disconnect();
+ try {
+ highLevelClient.connect(node.getHostname(), node.getPort());
+ } catch (UnknownHostException uhe1) {
+ fireNodeConnectionFailed(node, uhe1);
+ } catch (IOException ioe1) {
+ fireNodeConnectionFailed(node, ioe1);
+ }
+ }
+ }
+ }
+ }
+
}
* if an I/O error occurs while communicating with the node
*/
private void getRequests(final Node node) throws IOException {
- HighLevelClient highLevelClient = nodeManager.borrowHighLevelClient(node);
+ HighLevelClient highLevelClient = nodeManager.getHighLevelClient(node);
if (highLevelClient == null) {
logger.log(Level.WARNING, "no client for node: " + node);
return;
}
- try {
- HighLevelCallback<RequestListResult> requestListCallback = highLevelClient.getRequests();
- requestListCallback.addHighLevelCallbackListener(new HighLevelCallbackListener<RequestListResult>() {
+ HighLevelCallback<RequestListResult> requestListCallback = highLevelClient.getRequests();
+ requestListCallback.addHighLevelCallbackListener(new HighLevelCallbackListener<RequestListResult>() {
- @SuppressWarnings("synthetic-access")
- public void gotResult(HighLevelCallback<RequestListResult> highLevelCallback) {
- RequestListResult requestListResult;
- try {
- requestListResult = highLevelCallback.getResult();
- } catch (InterruptedException e) {
- logger.log(Level.SEVERE, "getResult() blocked and was interrupted");
- return;
- }
- for (RequestResult requestResult: requestListResult) {
- Request request = new Request(requestResult.getIdentifier());
- /* TODO - fill request */
- fireRequestAdded(node, request);
- }
+ @SuppressWarnings("synthetic-access")
+ public void gotResult(HighLevelCallback<RequestListResult> highLevelCallback) {
+ RequestListResult requestListResult;
+ try {
+ requestListResult = highLevelCallback.getResult();
+ } catch (InterruptedException e) {
+ logger.log(Level.SEVERE, "getResult() blocked and was interrupted");
+ return;
}
- });
- } finally {
- nodeManager.returnHighLevelClient(highLevelClient);
- }
+ for (RequestResult requestResult: requestListResult) {
+ Request request = new Request(requestResult.getIdentifier());
+ /* TODO - fill request */
+ fireRequestAdded(node, request);
+ }
+ }
+ });
}
//
* {@inheritDoc}
*/
public void nodeAdded(Node node) {
- HighLevelClient highLevelClient = nodeManager.borrowHighLevelClient(node);
+ HighLevelClient highLevelClient = nodeManager.getHighLevelClient(node);
if (highLevelClient == null) {
+ logger.warning("got nodeAdded but no high-level client: " + node);
return;
}
- try {
- highLevelClient.addHighLevelProgressListener(this);
- } finally {
- nodeManager.returnHighLevelClient(highLevelClient);
- }
+ highLevelClient.addHighLevelProgressListener(this);
}
/**
* {@inheritDoc}
*/
public void nodeRemoved(Node node) {
- /* ignore. */
+ HighLevelClient highLevelClient = nodeManager.getHighLevelClient(node);
+ if (highLevelClient == null) {
+ logger.warning("got nodeRemoved but no high-level client: " + node);
+ return;
+ }
+ highLevelClient.removeHighLevelProgressListener(this);
}
/**
* {@inheritDoc}
*/
public void nodeConnected(Node node) {
- HighLevelClient highLevelClient = nodeManager.borrowHighLevelClient(node);
+ HighLevelClient highLevelClient = nodeManager.getHighLevelClient(node);
if (highLevelClient == null) {
logger.log(Level.WARNING, "got no high-level client for node " + node);
return;
}
try {
highLevelClient.setWatchGlobal(true);
- } catch (IOException ioe1) {
- /* ignore exception, disconnects are handled elsewhere. */
- } finally {
- nodeManager.returnHighLevelClient(highLevelClient);
- }
- try {
getRequests(node);
} catch (IOException e) {
/* ignore exception, disconnects are handled elsewhere. */
/**
* {@inheritDoc}
*/
+ public void nodeConnectionFailed(Node node, Throwable cause) {
+ /* we don't care about this. */
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public void nodeDisconnected(Node node, Throwable throwable) {
- /* TODO - remove all requests. */
+ HighLevelClient highLevelClient = nodeManager.getHighLevelClient(node);
+ if (highLevelClient == null) {
+ logger.warning("got nodeDisconnected from node without high-level client: " + node);
+ return;
+ }
}
//
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.logging.Logger;
import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import net.pterodactylus.jsite.i18n.gui.I18nAction;
import net.pterodactylus.jsite.i18n.gui.I18nLabel;
import net.pterodactylus.jsite.main.Version;
+import net.pterodactylus.util.logging.Logging;
import net.pterodactylus.util.swing.SwingUtils;
/**
*/
public class ManageNodesDialog extends JDialog implements ListSelectionListener, I18nable {
+ /** The logger. */
+ @SuppressWarnings("unused")
+ private static final Logger logger = Logging.getLogger(ManageNodesDialog.class.getName());
+
/** The core. */
private final Core core;
newNode.setName(editNodeDialog.getNodeName());
newNode.setHostname(editNodeDialog.getNodeHostname());
newNode.setPort(editNodeDialog.getNodePort());
- nodeListModel.addNode(newNode);
+ try {
+ if (!core.addNode(newNode)) {
+ JOptionPane.showMessageDialog(this, I18n.get("manageNodesDialog.error.nodeAlreadyKnown.message", newNode.getHostname(), newNode.getPort()), I18n.get("manageNodesDialog.error.nodeAlreadyKnown.title"), JOptionPane.ERROR_MESSAGE);
+ }
+ } catch (UnknownHostException e) {
+ /*
+ * normally this shouldn't throw because the node editor catches
+ * it.
+ */
+ JOptionPane.showMessageDialog(this, I18n.get("manageNodesDialog.error.nodeUnresolvable.message", newNode.getHostname()), I18n.get("manageNodesDialog.error.nodeUnresolvable.title"), JOptionPane.ERROR_MESSAGE);
+ }
}
}
}
/**
- * List model for the {@link ManageNodesDialog#nodeList}. TODO
+ * List model for the {@link ManageNodesDialog#nodeList}.
*
* @author David ‘Bombe’ Roden <bombe@freenetproject.org>
* @version $Id$
if ((mainWindowX != -1) && (mainWindowY != -1) && (mainWindowWidth != -1) && (mainWindowHeight != -1)) {
mainWindow.setLocation(mainWindowX, mainWindowY);
mainWindow.setSize(mainWindowWidth, mainWindowHeight);
+ mainWindow.validate();
}
logWindow = new LogWindow();
}
nodeNodeDisconnectActions.clear();
nodeDisconnectActionNodes.clear();
for (Node node: nodes) {
- logger.finer("adding node “" + node + "” to menu");
+ logger.finer("adding node “" + node + "” to menus");
Action nodeConnectAction = new AbstractAction(node.getName()) {
/**
public void nodeAdded(Node node) {
logger.log(Level.INFO, "node added: " + node);
nodeList.add(node);
+ logger.log(Level.FINE, "nodeList.size(): " + nodeList.size());
manageNodesDialog.setNodeList(nodeList);
rebuildNodeActions(nodeList);
mainWindow.refreshNodeMenuItems();
/**
* {@inheritDoc}
*/
+ public void nodeConnectionFailed(Node node, Throwable cause) {
+ Action nodeConnectAction = nodeNodeConnectActions.get(node);
+ nodeConnectActions.add(nodeConnectAction);
+ mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectionToNodeFailed", node.getName(), node.getHostname(), node.getPort(), (cause != null) ? cause.getMessage() : "no reason given"));
+ JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.nodeConnectionFailed.message", node.getName(), node.getHostname(), node.getPort(), (cause != null) ? cause.getMessage() : "no reason given"), I18n.get("mainWindow.error.nodeConnectionFailed.title"), JOptionPane.ERROR_MESSAGE);
+ mainWindow.refreshNodeMenuItems();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public void nodeDisconnected(Node node, Throwable throwable) {
Action nodeConnectAction = nodeNodeConnectActions.get(node);
nodeConnectActions.add(nodeConnectAction);
*/
public void requestProgressed(Request request, int totalBlocks, int requiredBlocks, int successfulBlocks, int failedBlocks, int fatallyFailedBlocks, boolean finalizedTotal) {
/* TODO - update table model */
- mainWindow.setStatusBarText(request.getIdentifier() + " @ " + ((10000 * successfulBlocks / requiredBlocks) / 100.0));
+ mainWindow.setStatusBarText(request.getIdentifier() + " @ " + ((10000 * successfulBlocks / requiredBlocks) / 100.0) + "%");
}
//
mainWindow.error.projectLoadingFailed.title: Loading Projects Failed
mainWindow.error.projectLoadingFailed.message: Loading the projects from \u201c{0}\u201d has failed.
+mainWindow.error.nodeConnectionFailed.title: Connecting to Node Failed
+mainWindow.error.nodeConnectionFailed.message: Connecting to node \u201c{0}\u201d (at {1}:{2,number,#}) has failed: {3}
+
mainWindow.statusBar.coreLoaded: Core loaded.
mainWindow.statusBar.coreStopped: Core stopped.
mainWindow.statusBar.projectLoadingDone: Projects loaded.
mainWindow.statusBar.projectSavingDone: Projects saved.
mainWindow.statusBar.loadingNodesDone: Nodes loaded.
mainWindow.statusBar.savingNodesDone: Nodes saved.
-mainWindow.statusBar.connectingToNode: Connecting to \u201c{0}\u201d (at {1}:{2})\u2026
-mainWindow.statusBar.connectedToNode: Connected to \u201c{0}\u201d (at {1}:{2}).
-mainWindow.statusBar.disconnectedFromNode: Connected to \u201c{0}\u201d (at {1}:{2}).
+mainWindow.statusBar.connectingToNode: Connecting to \u201c{0}\u201d (at {1}:{2,number,#})\u2026
+mainWindow.statusBar.connectedToNode: Connected to \u201c{0}\u201d (at {1}:{2,number,#}).
+mainWindow.statusBar.connectionToNodeFailed: Connection to \u201c{0}\u201d (at {1}:{2,number,#}) failed: {3}
+mainWindow.statusBar.disconnectedFromNode: Disconnected from \u201c{0}\u201d (at {1}:{2,number,#}).
# main menus
mainWindow.menu.jSite.name: jSite
manageNodesDialog.error.nodeConnected.title: Node Is Connected
manageNodesDialog.error.nodeConnected.message: The Node \u201c{0}\u201d is still connected. Do you really want to delete it?
+manageNodesDialog.error.nodeUnresolvable.title: Node hostname can not be resolved
+manageNodesDialog.error.nodeUnresolvable.message: The hostname of the node (\u201c{0}\u201d) can not be resolved.
+
+manageNodesDialog.error.nodeAlreadyKnown.title: Node Already Known
+manageNodesDialog.error.nodeAlreadyKnown.message: There already is a node with the hostname (\u201c{0}\u201d) and port ({1,number,#}).
+
# the "edit node" dialog
editNodeDialog.title: Edit Node
mainWindow.error.projectLoadingFailed.title: Laden der Projekte fehlgeschlagen
mainWindow.error.projectLoadingFailed.message: Die Projekte aus \u201e{0}\u201c konnten nicht geladen werden.
+mainWindow.error.nodeConnectionFailed.title: Verbindung mit Node fehlgeschlagen
+mainWindow.error.nodeConnectionFailed.message: Die Verbindung zum Node \u201e{0}\u201c ({1}:{2,number,#}) konnte nicht hergestellt werden: {3}
+
mainWindow.statusBar.coreLoaded: Kern geladen.
mainWindow.statusBar.coreStopped: Kern angehalten.
mainWindow.statusBar.projectLoadingDone: Projekte geladen.
mainWindow.statusBar.projectSavingDone: Projekte gespeichert.
mainWindow.statusBar.loadingNodesDone: Nodes geladen.
mainWindow.statusBar.savingNodesDone: Nodes gespeichert.
-mainWindow.statusBar.connectingToNode: Verbindung zum Node \u201e{0}\u201c ({1}:{2}) wird hergestellt\u2026
-mainWindow.statusBar.connectedToNode: Verbindung zum Node \u201e{0}\u201c ({1}:{2}) hergestellt.
-mainWindow.statusBar.disconnectedFromNode: Verbindung von Node \u201e{0}\u201c ({1}:{2}) getrennt.
+mainWindow.statusBar.connectingToNode: Verbindung zum Node \u201e{0}\u201c ({1}:{2,number,#}) wird hergestellt\u2026
+mainWindow.statusBar.connectedToNode: Verbindung zum Node \u201e{0}\u201c ({1}:{2,number,#}) hergestellt.
+mainWindow.statusBar.connectionToNodeFailed: Verbindung zu Node \u201e{0}\u201c ({1}:{2,number,#}) fehlgeschlagen: {3}
+mainWindow.statusBar.disconnectedFromNode: Verbindung von Node \u201e{0}\u201c ({1}:{2,number,#}) getrennt.
# main menus
mainWindow.menu.jSite.name: jSite
manageNodesDialog.error.nodeConnected.title: Node ist verbunden
manageNodesDialog.error.nodeConnected.message: Mit dem Node \u201e{0}\u201c besteht momentan eine Verbindung. Wollen Sie ihn wirklich l\u00f6schen?
+manageNodesDialog.error.nodeUnresolvable.title: Hostname des Nodes ung\u00fcltig
+manageNodesDialog.error.nodeUnresolvable.message: Der Hostname des Nodes (\u201e{0}\u201c) kann nicht aufgel\u00f6st werden.
+
+manageNodesDialog.error.nodeAlreadyKnown.title: Node schon bekannt
+manageNodesDialog.error.nodeAlreadyKnown.message: Es gibt bereits einen Node mit diesem Hostnamen (\u201e{0}\u201c) und Port ({1,number,#}).
+
# the "edit node" dialog
editNodeDialog.title: Edit Node