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("useTestClock", valueFor(true))
fun toText() = toFileConfig().root().render(renderOptions)
fun toText(): String = toFileConfig().root().render(renderOptions)
fun moveTo(baseDir: Path) = NodeConfig(
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.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import net.corda.demobench.pty.R3Pty
import tornadofx.Controller
@ -27,13 +26,13 @@ class NodeController : Controller() {
private val cordaPath = jvm.applicationDir.resolve("corda").resolve("corda.jar")
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 var networkMapConfig: NetworkMapConfig? = null
val activeNodes: List<NodeConfig> get() = nodes.values.filter {
it.state == NodeState.RUNNING
(it.state == NodeState.RUNNING) || (it.state == NodeState.STARTING)
}
init {

View File

@ -3,10 +3,13 @@ package net.corda.demobench.profile
import com.google.common.net.HostAndPort
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
import java.io.File
import java.net.URI
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
import java.util.*
import java.util.function.BiPredicate
import javafx.stage.FileChooser
import javafx.stage.FileChooser.ExtensionFilter
import net.corda.demobench.model.*
@ -14,6 +17,10 @@ import tornadofx.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 baseDir = jvm.userHome.resolve("demobench")
private val nodeController by inject<NodeController>()
@ -21,34 +28,50 @@ class ProfileController : Controller() {
private val chooser = FileChooser()
init {
chooser.title = "DemoBench Profiles"
chooser.initialDirectory = baseDir.toFile()
chooser.extensionFilters.add(ExtensionFilter("DemoBench profiles (*.zip)", "*.zip", "*.ZIP"))
}
fun saveAs() {
log.info("Save as")
}
fun saveProfile(): Boolean {
var target = chooser.showSaveDialog(null) ?: return false
if (target.extension.isEmpty()) {
target = File(target.parent, target.name + ".zip")
}
fun save() {
log.info("Save")
log.info("Save profile as: $target")
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>? {
val chosen = chooser.showOpenDialog(null) ?: return null
log.info("Selected profile: ${chosen}")
log.info("Selected profile: $chosen")
val configs = LinkedList<NodeConfig>()
FileSystems.newFileSystem(chosen.toPath(), null).use {
fs -> fs.rootDirectories.forEach {
root -> Files.walk(root).forEach {
if ((it.nameCount == 2) && ("node.conf" == it.fileName.toString())) {
try {
configs.add(toNodeConfig(parse(it)))
} catch (e: Exception) {
log.severe("Failed to parse '$it': ${e.message}")
throw e
}
root -> Files.find(root, 2, ConfigAcceptor).forEach {
try {
// 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) {
log.severe("Failed to parse '$it': ${e.message}")
throw e
}
}
}

View File

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

View File

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

View File

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

View File

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