From 029548705a1fa60731ad7f492103333acdd48001 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Sat, 3 May 2008 19:21:18 +0000 Subject: [PATCH] add more status notifications to core listener git-svn-id: http://trooper/svn/projects/jSite/trunk@784 c3eda9e8-030b-0410-8277-bc7414b0a119 --- src/net/pterodactylus/jsite/core/Core.java | 107 +++++++- src/net/pterodactylus/jsite/core/CoreListener.java | 50 +++- src/net/pterodactylus/jsite/core/NodeManager.java | 299 +++++++++++++++++++++ .../pterodactylus/jsite/gui/SwingInterface.java | 36 +++ src/net/pterodactylus/jsite/i18n/jSite.properties | 3 + .../pterodactylus/jsite/i18n/jSite_de.properties | 4 + src/net/pterodactylus/jsite/main/Main.java | 10 +- 7 files changed, 498 insertions(+), 11 deletions(-) create mode 100644 src/net/pterodactylus/jsite/core/NodeManager.java diff --git a/src/net/pterodactylus/jsite/core/Core.java b/src/net/pterodactylus/jsite/core/Core.java index 7c37808..9aa60cf 100644 --- a/src/net/pterodactylus/jsite/core/Core.java +++ b/src/net/pterodactylus/jsite/core/Core.java @@ -37,11 +37,8 @@ public class Core { /** The project manager. */ private ProjectManager projectManager; - /** The node list. */ - private List configuredNodes = new ArrayList(); - - /** List of currently connected nodes. */ - private List connectedNodes = new ArrayList(); + /** The node manager. */ + private NodeManager nodeManager; // // LISTENER MANAGEMENT @@ -68,6 +65,18 @@ public class Core { } /** + * Notifies all listeners that the projects were loaded successfully. + * + * @param directory + * The directory the projects were loaded from + */ + private void fireLoadingProjectsDone(String directory) { + for (CoreListener coreListener: coreListeners) { + coreListener.loadingProjectsDone(directory); + } + } + + /** * Notifies all core listeners that loading the projects from the given * directory has failed. * @@ -109,6 +118,58 @@ public class Core { } /** + * Notifies all listeners that the nodes were successfully loaded. + * + * @param directory + * The directory the nodes were loaded from + */ + private void fireLoadingNodesDone(String directory) { + for (CoreListener coreListener: coreListeners) { + coreListener.loadingNodesDone(directory); + } + } + + /** + * Notifies all listeners that loading the nodes has failed. + * + * @param directory + * The directory the nodes were loaded from + * @param throwable + * The exception that occured while loading the nodes + */ + private void fireLoadingNodesFailed(String directory, Throwable throwable) { + for (CoreListener coreListener: coreListeners) { + coreListener.loadingNodesFailed(directory, throwable); + } + } + + /** + * Notifies all listeners that the nodes were saved successfully. + * + * @param directory + * The directory the nodes were saved to + */ + private void fireSavingNodesDone(String directory) { + for (CoreListener coreListener: coreListeners) { + coreListener.savingNodesDone(directory); + } + } + + /** + * Notifies all listeners that saving the nodes has failed. + * + * @param directory + * The directory the nodes were saved to + * @param throwable + * The exception that occured while saving the nodes + */ + private void fireSavingNodesFailed(String directory, Throwable throwable) { + for (CoreListener coreListener: coreListeners) { + coreListener.savingProjectsFailed(directory, throwable); + } + } + + /** * Notifies all core listeners that the core has loaded and is ready to run. */ private void fireCoreLoaded() { @@ -150,12 +211,31 @@ public class Core { } /** + * Returns the node manager. + * + * @return The node manager + */ + public NodeManager getNodeManager() { + return nodeManager; + } + + /** + * Sets the node manager to use. + * + * @param nodeManager + * The node manager to use + */ + public void setNodeManager(NodeManager nodeManager) { + this.nodeManager = nodeManager; + } + + /** * Returns the list of all configured nodes. * * @return All configured nodes */ public List getNodes() { - return configuredNodes; + return nodeManager.getNodes(); } /** @@ -167,7 +247,7 @@ public class Core { * node, false otherwise */ public boolean isNodeConnected(Node node) { - return connectedNodes.contains(node); + return nodeManager.hasNode(node); } // @@ -180,9 +260,16 @@ public class Core { public void start() { try { projectManager.load(); + fireLoadingProjectsDone(projectManager.getDirectory()); } catch (IOException ioe1) { fireLoadingProjectsFailed(projectManager.getDirectory(), ioe1); } + try { + nodeManager.load(); + fireLoadingNodesDone(nodeManager.getDirectory()); + } catch (IOException ioe1) { + fireLoadingNodesFailed(nodeManager.getDirectory(), ioe1); + } fireCoreLoaded(); } @@ -196,6 +283,12 @@ public class Core { } catch (IOException ioe1) { fireSavingProjectsFailed(projectManager.getDirectory(), ioe1); } + try { + nodeManager.save(); + fireSavingNodesDone(nodeManager.getDirectory()); + } catch (IOException ioe1) { + fireSavingNodesFailed(nodeManager.getDirectory(), ioe1); + } fireCoreStopped(); } diff --git a/src/net/pterodactylus/jsite/core/CoreListener.java b/src/net/pterodactylus/jsite/core/CoreListener.java index 7ded200..812ce89 100644 --- a/src/net/pterodactylus/jsite/core/CoreListener.java +++ b/src/net/pterodactylus/jsite/core/CoreListener.java @@ -28,10 +28,18 @@ package net.pterodactylus.jsite.core; public interface CoreListener { // - // configuration stuff + // project configuration // /** + * Notifies a listener that loading the projects finished successfully. + * + * @param directory + * The directory the nodes were loaded from + */ + public void loadingProjectsDone(String directory); + + /** * Notifies all listeners that loading the projects has failed. * * @param directory @@ -61,6 +69,46 @@ public interface CoreListener { public void savingProjectsFailed(String directory, Throwable throwable); // + // node configuration + // + + /** + * Notifies a listener that the nodes were successfully loaded. + * + * @param directory + * The directory the nodes were loaded from + */ + public void loadingNodesDone(String directory); + + /** + * Notifies a listener that loading the nodes has failed. + * + * @param directory + * The directory the nodes were loaded from + * @param throwable + * The exception that occured while loading the nodes + */ + public void loadingNodesFailed(String directory, Throwable throwable); + + /** + * Notifies a listener that the nodes were successfully saved. + * + * @param directory + * The directory the nodes were saved to + */ + public void savingNodesDone(String directory); + + /** + * Notifies a listener that saving the nodes has failed. + * + * @param directory + * The directory the nodes were saved to + * @param throwable + * The exception that occured while saving the nodes + */ + public void savingNodesFailed(String directory, Throwable throwable); + + // // basic core functionality // diff --git a/src/net/pterodactylus/jsite/core/NodeManager.java b/src/net/pterodactylus/jsite/core/NodeManager.java new file mode 100644 index 0000000..71bb95d --- /dev/null +++ b/src/net/pterodactylus/jsite/core/NodeManager.java @@ -0,0 +1,299 @@ +/* + * jSite2 - FcpCollector.java - + * Copyright © 2008 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package net.pterodactylus.jsite.core; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +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; + +import net.pterodactylus.fcp.highlevel.ConnectResult; +import net.pterodactylus.fcp.highlevel.HighLevelCallback; +import net.pterodactylus.fcp.highlevel.HighLevelClient; +import net.pterodactylus.util.io.Closer; + +/** + * TODO + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + * @version $Id$ + */ +public class NodeManager { + + /** Logger. */ + private static final Logger logger = Logger.getLogger(NodeManager.class.getName()); + + /** The FCP client name. */ + private final String clientName; + + /** The directory for the configuration. */ + private final String directory; + + /** Object used for synchronization. */ + private final Object syncObject = new Object(); + + /** All nodes. */ + private List nodes = Collections.synchronizedList(new ArrayList()); + + /** All FCP connections. */ + private Map nodeConnections = Collections.synchronizedMap(new HashMap()); + + /** Keeps track of which connection is in use right now. */ + private Set usedConnections = Collections.synchronizedSet(new HashSet()); + + /** Maps nodes to high-level clients. */ + private Map clientNodes = Collections.synchronizedMap(new HashMap()); + + /** + * Creates a new FCP collector. + * + * @param clientName + * The name of the FCP client + * @param directory + * The directory in which to store the nodes + */ + public NodeManager(String clientName, String directory) { + this.clientName = clientName; + this.directory = directory; + } + + // + // ACCESSORS + // + + /** + * Returns the directory in which the nodes are stored. + * + * @return The directory the nodes are stored in + */ + public String getDirectory() { + return directory; + } + + /** + * Checks whether the given node is already connected. + * + * @param node + * The node to check + * @return true if the node is already connected, + * false otherwise + */ + public boolean hasNode(Node node) { + return nodes.contains(node); + } + + // + // ACTIONS + // + + /** + * Loads nodes. + * + * @throws IOException + * if an I/O error occurs loading the nodes + */ + public void load() throws IOException { + File directoryFile = new File(directory); + File nodeFile = new File(directoryFile, "nodes.properties"); + if (!nodeFile.exists() || !nodeFile.isFile() || !nodeFile.canRead()) { + return; + } + Properties nodeProperties = new Properties(); + InputStream nodeInputStream = null; + try { + nodeInputStream = new FileInputStream(nodeFile); + nodeProperties.load(nodeInputStream); + } finally { + Closer.close(nodeInputStream); + } + int nodeIndex = -1; + List loadedNodes = new ArrayList(); + while (nodeProperties.containsKey("nodes." + ++nodeIndex + ".name")) { + String nodePrefix = "nodes." + nodeIndex; + String nodeName = nodeProperties.getProperty(nodePrefix + ".name"); + if (!Verifier.verifyNodeName(nodeName)) { + logger.log(Level.WARNING, "invalid node name “" + nodeName + "”, skipping…"); + continue; + } + String nodeHostname = nodeProperties.getProperty(nodePrefix + ".hostname"); + if (!Verifier.verifyHostname(nodeHostname)) { + logger.log(Level.WARNING, "invalid host name “" + nodeHostname + "”"); + /* not fatal, might be valid later on. */ + } + String nodePortString = nodeProperties.getProperty(nodePrefix + ".port"); + if (!Verifier.verifyPort(nodePortString)) { + logger.log(Level.WARNING, "invalid port number “" + nodePortString + "”, skipping…"); + continue; + } + int nodePort = -1; + try { + nodePort = Integer.valueOf(nodePortString); + } catch (NumberFormatException nfe1) { + /* shouldn't happen, port number was checked before. */ + logger.log(Level.SEVERE, "invalid port number “" + nodePortString + "”, check failed! skipping…"); + continue; + } + Node newNode = new Node(); + newNode.setName(nodeName); + newNode.setHostname(nodeHostname); + newNode.setPort(nodePort); + loadedNodes.add(newNode); + } + synchronized (syncObject) { + nodes.clear(); + nodes.addAll(loadedNodes); + } + } + + /** + * Saves all configured nodes. + * + * @throws IOException + * if an I/O error occurs saving the nodes + */ + public void save() throws IOException { + File directoryFile = new File(directory); + if (!directoryFile.exists()) { + if (!directoryFile.mkdirs()) { + throw new IOException("could not create directory: " + directory); + } + } + Properties nodeProperties = new Properties(); + int nodeIndex = -1; + for (Node node: nodes) { + String nodePrefix = "nodes." + ++nodeIndex; + nodeProperties.setProperty(nodePrefix + ".name", node.getName()); + nodeProperties.setProperty(nodePrefix + ".hostname", node.getHostname()); + nodeProperties.setProperty(nodePrefix + ".port", String.valueOf(node.getPort())); + } + File projectFile = new File(directoryFile, "nodes.properties"); + OutputStream nodeOutputStream = null; + try { + nodeOutputStream = new FileOutputStream(projectFile); + nodeProperties.store(nodeOutputStream, "jSite nodes"); + } finally { + Closer.close(nodeOutputStream); + } + } + + /** + * Adds a connection to the given node. The connection is made instantly so + * this method may block. If the node can not be connected, it will not be + * added to the list of nodes. + * + * @param node + * The node to connect to + * @return true if the connection to the node could be + * established + * @throws UnknownHostException + * if the hostname of the node can not be resolved + * @throws IOException + * if an I/O error occurs connecting to the node + */ + public boolean addNode(Node node) throws UnknownHostException, IOException { + if (nodes.contains(node)) { + return true; + } + HighLevelClient highLevelClient = new HighLevelClient(clientName, node.getHostname(), node.getPort()); + HighLevelCallback connectCallback = highLevelClient.connect(); + ConnectResult connectResult = null; + while (connectResult == null) { + try { + connectResult = connectCallback.getResult(); + } catch (InterruptedException e) { + /* ignore. */ + } + } + if (connectResult.isConnected()) { + synchronized (syncObject) { + nodes.add(node); + nodeConnections.put(node, highLevelClient); + clientNodes.put(highLevelClient, node); + } + } + return connectResult.isConnected(); + } + + /** + * Returns a list of all nodes. + * + * @return A list of all nodes + */ + public List getNodes() { + return new ArrayList(clientNodes.values()); + } + + // + // PRIVATE METHODS + // + + /** + * Finds a currently unused high-level client, optionally waiting until a + * client is free and marking it used. + * + * @param wait + * true to wait for a free connection, + * false to return null + * @param markAsUsed + * true to mark the connection as used before + * returning it, false not to mark it + * @return An unused FCP connection, or null 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: nodeConnections.values()) { + if (!usedConnections.contains(highLevelClient)) { + freeHighLevelClient = highLevelClient; + break; + } + } + if (freeHighLevelClient != null) { + if (markAsUsed) { + usedConnections.add(freeHighLevelClient); + } + return freeHighLevelClient; + } + if (!wait) { + return null; + } + } + /* we never get here, but the compiler doesn't realize. */ + return null; + } + } + +} diff --git a/src/net/pterodactylus/jsite/gui/SwingInterface.java b/src/net/pterodactylus/jsite/gui/SwingInterface.java index b9eb177..e022cf1 100644 --- a/src/net/pterodactylus/jsite/gui/SwingInterface.java +++ b/src/net/pterodactylus/jsite/gui/SwingInterface.java @@ -622,6 +622,14 @@ public class SwingInterface implements CoreListener { // INTERFACE CoreListener // + + /** + * {@inheritDoc} + */ + public void loadingProjectsDone(String directory) { + mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectLoadingDone")); + } + /** * {@inheritDoc} */ @@ -646,6 +654,34 @@ public class SwingInterface implements CoreListener { /** * {@inheritDoc} */ + public void loadingNodesDone(String directory) { + mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.loadingNodesDone")); + } + + /** + * {@inheritDoc} + */ + public void loadingNodesFailed(String directory, Throwable throwable) { + /* TODO */ + } + + /** + * {@inheritDoc} + */ + public void savingNodesDone(String directory) { + mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.savingNodesDone")); + } + + /** + * {@inheritDoc} + */ + public void savingNodesFailed(String directory, Throwable throwable) { + /* TODO */ + } + + /** + * {@inheritDoc} + */ public void coreLoaded() { this.nodeList = core.getNodes(); manageNodesDialog.setNodeList(nodeList); diff --git a/src/net/pterodactylus/jsite/i18n/jSite.properties b/src/net/pterodactylus/jsite/i18n/jSite.properties index a422f0a..014da26 100644 --- a/src/net/pterodactylus/jsite/i18n/jSite.properties +++ b/src/net/pterodactylus/jsite/i18n/jSite.properties @@ -44,7 +44,10 @@ mainWindow.error.projectLoadingFailed.message: Loading the projects from \u201c{ 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. # main menus mainWindow.menu.jSite.name: jSite diff --git a/src/net/pterodactylus/jsite/i18n/jSite_de.properties b/src/net/pterodactylus/jsite/i18n/jSite_de.properties index bdee581..e1a1a77 100644 --- a/src/net/pterodactylus/jsite/i18n/jSite_de.properties +++ b/src/net/pterodactylus/jsite/i18n/jSite_de.properties @@ -44,7 +44,11 @@ mainWindow.error.projectLoadingFailed.message: Die Projekte aus \u201e{0}\u201c 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. + # main menus mainWindow.menu.jSite.name: jSite diff --git a/src/net/pterodactylus/jsite/main/Main.java b/src/net/pterodactylus/jsite/main/Main.java index bf6f5a6..0c7e637 100644 --- a/src/net/pterodactylus/jsite/main/Main.java +++ b/src/net/pterodactylus/jsite/main/Main.java @@ -22,12 +22,13 @@ package net.pterodactylus.jsite.main; import java.io.File; import net.pterodactylus.jsite.core.Core; +import net.pterodactylus.jsite.core.NodeManager; import net.pterodactylus.jsite.core.ProjectManager; import net.pterodactylus.jsite.gui.SwingInterface; /** * Main class that is called by the VM. - * + * * @author David ‘Bombe’ Roden <bombe@freenetproject.org> * @version $Id$ */ @@ -35,7 +36,7 @@ public class Main { /** * Main entry method for the VM. - * + * * @param args * The command-line arguments */ @@ -50,10 +51,13 @@ public class Main { Core core = new Core(); String configDirectory = System.getProperty("user.home") + File.separator + ".jSite"; - + ProjectManager projectManager = new ProjectManager(configDirectory); core.setProjectManager(projectManager); + NodeManager nodeManager = new NodeManager("jSite-" + Version.getVersion(), configDirectory); + core.setNodeManager(nodeManager); + SwingInterface swingInterface = new SwingInterface(core, configDirectory); core.addCoreListener(swingInterface); swingInterface.start(); -- 2.7.4