CORPRIV-661: Implement saving profiles.

This commit is contained in:
Chris Rankin 2017-02-20 15:23:27 +00:00
parent ddd8d6a513
commit 0f73b68d39
7 changed files with 94 additions and 52 deletions

View File

@ -66,7 +66,7 @@ class NodeConfig(
.withValue("h2port", valueFor(h2Port)) .withValue("h2port", valueFor(h2Port))
.withValue("useTestClock", valueFor(true)) .withValue("useTestClock", valueFor(true))
fun toText() = toFileConfig().root().render(renderOptions) fun toText(): String = toFileConfig().root().render(renderOptions)
fun moveTo(baseDir: Path) = NodeConfig( fun moveTo(baseDir: Path) = NodeConfig(
baseDir, legalName, artemisPort, nearestCity, webPort, h2Port, extraServices, users baseDir, legalName, artemisPort, nearestCity, webPort, h2Port, extraServices, users

View File

@ -5,7 +5,6 @@ import java.lang.management.ManagementFactory
import java.net.ServerSocket import java.net.ServerSocket
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import net.corda.demobench.pty.R3Pty import net.corda.demobench.pty.R3Pty
import tornadofx.Controller import tornadofx.Controller
@ -27,13 +26,13 @@ class NodeController : Controller() {
private val cordaPath = jvm.applicationDir.resolve("corda").resolve("corda.jar") private val cordaPath = jvm.applicationDir.resolve("corda").resolve("corda.jar")
private val command = jvm.commandFor(cordaPath) private val command = jvm.commandFor(cordaPath)
private val nodes = ConcurrentHashMap<String, NodeConfig>() private val nodes = LinkedHashMap<String, NodeConfig>()
private val port = AtomicInteger(FIRST_PORT) private val port = AtomicInteger(FIRST_PORT)
private var networkMapConfig: NetworkMapConfig? = null private var networkMapConfig: NetworkMapConfig? = null
val activeNodes: List<NodeConfig> get() = nodes.values.filter { val activeNodes: List<NodeConfig> get() = nodes.values.filter {
it.state == NodeState.RUNNING (it.state == NodeState.RUNNING) || (it.state == NodeState.STARTING)
} }
init { init {

View File

@ -3,10 +3,13 @@ package net.corda.demobench.profile
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import java.nio.file.FileSystems import java.io.File
import java.nio.file.Files import java.net.URI
import java.nio.file.Path import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
import java.util.* import java.util.*
import java.util.function.BiPredicate
import javafx.stage.FileChooser import javafx.stage.FileChooser
import javafx.stage.FileChooser.ExtensionFilter import javafx.stage.FileChooser.ExtensionFilter
import net.corda.demobench.model.* import net.corda.demobench.model.*
@ -14,6 +17,10 @@ import tornadofx.Controller
class ProfileController : Controller() { class ProfileController : Controller() {
private companion object ConfigAcceptor : BiPredicate<Path, BasicFileAttributes> {
override fun test(p: Path?, attr: BasicFileAttributes?) = "node.conf" == p?.fileName.toString()
}
private val jvm by inject<JVMConfig>() private val jvm by inject<JVMConfig>()
private val baseDir = jvm.userHome.resolve("demobench") private val baseDir = jvm.userHome.resolve("demobench")
private val nodeController by inject<NodeController>() private val nodeController by inject<NodeController>()
@ -21,30 +28,47 @@ class ProfileController : Controller() {
private val chooser = FileChooser() private val chooser = FileChooser()
init { init {
chooser.title = "DemoBench Profiles"
chooser.initialDirectory = baseDir.toFile() chooser.initialDirectory = baseDir.toFile()
chooser.extensionFilters.add(ExtensionFilter("DemoBench profiles (*.zip)", "*.zip", "*.ZIP")) chooser.extensionFilters.add(ExtensionFilter("DemoBench profiles (*.zip)", "*.zip", "*.ZIP"))
} }
fun saveAs() { fun saveProfile(): Boolean {
log.info("Save as") var target = chooser.showSaveDialog(null) ?: return false
if (target.extension.isEmpty()) {
target = File(target.parent, target.name + ".zip")
} }
fun save() { log.info("Save profile as: $target")
log.info("Save")
val configs = nodeController.activeNodes
FileSystems.newFileSystem(URI.create("jar:" + target.toURI()), mapOf("create" to "true")).use {
fs -> configs.forEach { it ->
val nodeDir = Files.createDirectories(fs.getPath(it.key))
val conf = Files.write(nodeDir.resolve("node.conf"), it.toText().toByteArray(UTF_8))
log.info("Wrote: $conf")
}
}
return true
} }
fun openProfile(): List<NodeConfig>? { fun openProfile(): List<NodeConfig>? {
val chosen = chooser.showOpenDialog(null) ?: return null val chosen = chooser.showOpenDialog(null) ?: return null
log.info("Selected profile: ${chosen}") log.info("Selected profile: $chosen")
val configs = LinkedList<NodeConfig>() val configs = LinkedList<NodeConfig>()
FileSystems.newFileSystem(chosen.toPath(), null).use { FileSystems.newFileSystem(chosen.toPath(), null).use {
fs -> fs.rootDirectories.forEach { fs -> fs.rootDirectories.forEach {
root -> Files.walk(root).forEach { root -> Files.find(root, 2, ConfigAcceptor).forEach {
if ((it.nameCount == 2) && ("node.conf" == it.fileName.toString())) {
try { try {
configs.add(toNodeConfig(parse(it))) // Java seems to "walk" through the ZIP file backwards.
// So add new config to the front of the list, so that
// our final list is ordered to match the file.
configs.addFirst(toNodeConfig(parse(it)))
log.info("Loaded: $it")
} catch (e: Exception) { } catch (e: Exception) {
log.severe("Failed to parse '$it': ${e.message}") log.severe("Failed to parse '$it': ${e.message}")
throw e throw e
@ -52,7 +76,6 @@ class ProfileController : Controller() {
} }
} }
} }
}
return configs return configs
} }

View File

@ -10,7 +10,7 @@ import net.corda.node.services.messaging.CordaRPCClient
class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Unit): AutoCloseable { class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Unit): AutoCloseable {
private companion object Data { private companion object {
val log = loggerFor<NodeRPC>() val log = loggerFor<NodeRPC>()
val ONE_SECOND = SECONDS.toMillis(1) val ONE_SECOND = SECONDS.toMillis(1)
} }

View File

@ -23,13 +23,20 @@ class DemoBenchView : View("Corda Demo Bench") {
private val addNodeButton by fxid<Button>() private val addNodeButton by fxid<Button>()
private val nodeTabPane by fxid<TabPane>() private val nodeTabPane by fxid<TabPane>()
private val menuOpen by fxid<MenuItem>() private val menuOpen by fxid<MenuItem>()
private val menuSave by fxid<MenuItem>()
private val menuSaveAs by fxid<MenuItem>() private val menuSaveAs by fxid<MenuItem>()
init { init {
importStylesheet("/net/corda/demobench/style.css") importStylesheet("/net/corda/demobench/style.css")
primaryStage.setOnCloseRequest { configureShutdown()
configureProfileSaveAs()
configureProfileOpen()
configureAddNode()
}
private fun configureShutdown() = primaryStage.setOnCloseRequest {
log.info("Exiting") log.info("Exiting")
// Prevent any new NodeTabViews from being created. // Prevent any new NodeTabViews from being created.
@ -39,13 +46,17 @@ class DemoBenchView : View("Corda Demo Bench") {
Platform.exit() Platform.exit()
} }
menuSaveAs.setOnAction { private fun configureProfileSaveAs() = menuSaveAs.setOnAction {
profileController.saveAs() try {
if (profileController.saveProfile()) {
menuSaveAs.isDisable = true
} }
menuSave.setOnAction { } catch (e: Exception) {
profileController.save() ExceptionDialog(e).apply { initOwner(root.scene.window) }.showAndWait()
} }
menuOpen.setOnAction { }
private fun configureProfileOpen() = menuOpen.setOnAction {
try { try {
val profile = profileController.openProfile() val profile = profileController.openProfile()
if (profile != null) { if (profile != null) {
@ -56,6 +67,7 @@ class DemoBenchView : View("Corda Demo Bench") {
} }
} }
private fun configureAddNode() {
addNodeButton.setOnAction { addNodeButton.setOnAction {
val nodeTabView = createNodeTabView(true) val nodeTabView = createNodeTabView(true)
nodeTabPane.selectionModel.select(nodeTabView.nodeTab) nodeTabPane.selectionModel.select(nodeTabView.nodeTab)
@ -66,19 +78,17 @@ class DemoBenchView : View("Corda Demo Bench") {
addNodeButton.fire() addNodeButton.fire()
} }
private fun closeAllTabs() { private fun closeAllTabs() = ArrayList<Tab>(nodeTabPane.tabs).forEach {
ArrayList<Tab>(nodeTabPane.tabs).forEach {
(it as CloseableTab).requestClose() (it as CloseableTab).requestClose()
} }
}
fun createNodeTabView(showConfig: Boolean): NodeTabView { private fun createNodeTabView(showConfig: Boolean): NodeTabView {
val nodeTabView = find<NodeTabView>(mapOf("showConfig" to showConfig)) val nodeTabView = find<NodeTabView>(mapOf("showConfig" to showConfig))
nodeTabPane.tabs.add(nodeTabView.nodeTab) nodeTabPane.tabs.add(nodeTabView.nodeTab)
return nodeTabView return nodeTabView
} }
fun loadProfile(nodes: List<NodeConfig>) { private fun loadProfile(nodes: List<NodeConfig>) {
closeAllTabs() closeAllTabs()
nodeController.reset() nodeController.reset()
@ -90,6 +100,16 @@ class DemoBenchView : View("Corda Demo Bench") {
enableAddNodes() enableAddNodes()
} }
/**
* Enable the "save profile" menu item.
*/
fun enableSaveProfile() {
menuSaveAs.isDisable = false
}
/**
* Enables the button that allows us to create a new node.
*/
fun enableAddNodes() { fun enableAddNodes() {
addNodeButton.isDisable = false addNodeButton.isDisable = false
} }

View File

@ -155,6 +155,7 @@ class NodeTabView : Fragment() {
if (model.validate()) { if (model.validate()) {
launch() launch()
main.enableAddNodes() main.enableAddNodes()
main.enableSaveProfile()
} }
} }
} }

View File

@ -13,8 +13,7 @@
<MenuBar> <MenuBar>
<Menu text="File"> <Menu text="File">
<MenuItem fx:id="menuOpen" text="Open"/> <MenuItem fx:id="menuOpen" text="Open"/>
<MenuItem fx:id="menuSave" text="Save"/> <MenuItem fx:id="menuSaveAs" disable="true" text="Save As"/>
<MenuItem fx:id="menuSaveAs" text="Save As"/>
</Menu> </Menu>
</MenuBar> </MenuBar>
<StackPane> <StackPane>