DemoBench now subscribes to updates for transaction and vault RPCs. (#571)

* Subscribe to updates for transaction and vault RPCs.
* Ensure we unsubscribe our observables at the end.
* Use Rx scheduler that can observe on FX application thread.
This commit is contained in:
Chris Rankin
2017-04-24 17:59:51 +01:00
committed by GitHub
parent c1b7b1cb75
commit ab8bfec76f
3 changed files with 63 additions and 37 deletions

View File

@ -8,7 +8,7 @@ import net.corda.demobench.model.NodeConfig
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeUnit.SECONDS
class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Unit) : AutoCloseable { class NodeRPC(config: NodeConfig, start: (NodeConfig, CordaRPCOps) -> Unit, invoke: (CordaRPCOps) -> Unit) : AutoCloseable {
private companion object { private companion object {
val log = loggerFor<NodeRPC>() val log = loggerFor<NodeRPC>()
@ -30,7 +30,7 @@ class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Un
this.cancel() this.cancel()
// Run "start-up" task, now that the RPC client is ready. // Run "start-up" task, now that the RPC client is ready.
start() start(config, ops)
// Schedule a new task that will refresh the display once per second. // Schedule a new task that will refresh the display once per second.
timer.schedule(object : TimerTask() { timer.schedule(object : TimerTask() {

View File

@ -3,17 +3,22 @@ package net.corda.demobench.views
import com.jediterm.terminal.TerminalColor import com.jediterm.terminal.TerminalColor
import com.jediterm.terminal.TextStyle import com.jediterm.terminal.TextStyle
import com.jediterm.terminal.ui.settings.DefaultSettingsProvider import com.jediterm.terminal.ui.settings.DefaultSettingsProvider
import java.awt.Dimension
import java.net.URI
import java.util.logging.Level
import javax.swing.SwingUtilities
import javafx.application.Platform import javafx.application.Platform
import javafx.embed.swing.SwingNode import javafx.embed.swing.SwingNode
import javafx.scene.control.Button import javafx.scene.control.Button
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.image.ImageView import javafx.scene.image.ImageView
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.scene.layout.HBox
import javafx.scene.layout.VBox import javafx.scene.layout.VBox
import javafx.util.Duration import javafx.util.Duration
import net.corda.client.rpc.notUsed
import net.corda.core.success import net.corda.core.success
import net.corda.core.then import net.corda.core.then
import net.corda.core.messaging.CordaRPCOps
import net.corda.demobench.explorer.ExplorerController import net.corda.demobench.explorer.ExplorerController
import net.corda.demobench.model.NodeConfig import net.corda.demobench.model.NodeConfig
import net.corda.demobench.model.NodeController import net.corda.demobench.model.NodeController
@ -23,11 +28,9 @@ import net.corda.demobench.rpc.NodeRPC
import net.corda.demobench.ui.PropertyLabel import net.corda.demobench.ui.PropertyLabel
import net.corda.demobench.web.DBViewer import net.corda.demobench.web.DBViewer
import net.corda.demobench.web.WebServerController import net.corda.demobench.web.WebServerController
import rx.Subscription
import rx.schedulers.Schedulers
import tornadofx.* import tornadofx.*
import java.awt.Dimension
import java.net.URI
import java.util.logging.Level
import javax.swing.SwingUtilities
class NodeTerminalView : Fragment() { class NodeTerminalView : Fragment() {
override val root by fxml<VBox>() override val root by fxml<VBox>()
@ -41,10 +44,14 @@ class NodeTerminalView : Fragment() {
private val transactions by fxid<PropertyLabel>() private val transactions by fxid<PropertyLabel>()
private val balance by fxid<PropertyLabel>() private val balance by fxid<PropertyLabel>()
private val header by fxid<HBox>()
private val viewDatabaseButton by fxid<Button>() private val viewDatabaseButton by fxid<Button>()
private val launchWebButton by fxid<Button>() private val launchWebButton by fxid<Button>()
private val launchExplorerButton by fxid<Button>() private val launchExplorerButton by fxid<Button>()
private val subscriptions: MutableList<Subscription> = mutableListOf()
private var txCount: Int = 0
private var stateCount: Int = 0
private var isDestroyed: Boolean = false private var isDestroyed: Boolean = false
private val explorer = explorerController.explorer() private val explorer = explorerController.explorer()
private val webServer = webServerController.webServer() private val webServer = webServerController.webServer()
@ -98,22 +105,13 @@ class NodeTerminalView : Fragment() {
}) })
} }
fun enable(config: NodeConfig) {
config.state = NodeState.RUNNING
log.info("Node '${config.legalName}' is now ready.")
launchExplorerButton.isDisable = false
viewDatabaseButton.isDisable = false
launchWebButton.isDisable = false
}
/* /*
* We only want to run one explorer for each node. * We only want to run one explorer for each node.
* So disable the "launch" button when we have * So disable the "launch" button when we have
* launched the explorer and only reenable it when * launched the explorer and only reenable it when
* the explorer has exited. * the explorer has exited.
*/ */
fun configureExplorerButton(config: NodeConfig) { private fun configureExplorerButton(config: NodeConfig) {
launchExplorerButton.setOnAction { launchExplorerButton.setOnAction {
launchExplorerButton.isDisable = true launchExplorerButton.isDisable = true
@ -123,7 +121,7 @@ class NodeTerminalView : Fragment() {
} }
} }
fun configureDatabaseButton(config: NodeConfig) { private fun configureDatabaseButton(config: NodeConfig) {
viewDatabaseButton.setOnAction { viewDatabaseButton.setOnAction {
viewer.openBrowser(config.h2Port) viewer.openBrowser(config.h2Port)
} }
@ -137,7 +135,7 @@ class NodeTerminalView : Fragment() {
* launched the web server and only reenable it when * launched the web server and only reenable it when
* the web server has exited. * the web server has exited.
*/ */
fun configureWebButton(config: NodeConfig) { private fun configureWebButton(config: NodeConfig) {
launchWebButton.setOnAction { launchWebButton.setOnAction {
if (webURL != null) { if (webURL != null) {
app.hostServices.showDocument(webURL.toString()) app.hostServices.showDocument(webURL.toString())
@ -158,28 +156,62 @@ class NodeTerminalView : Fragment() {
} }
} }
fun launchRPC(config: NodeConfig) = NodeRPC(config, start = { enable(config) }, invoke = { ops -> private fun launchRPC(config: NodeConfig) = NodeRPC(
config = config,
start = this::initialise,
invoke = this::pollCashBalances
)
private fun initialise(config: NodeConfig, ops: CordaRPCOps) {
try {
val (txInit, txNext) = ops.verifiedTransactions()
val (stateInit, stateNext) = ops.vaultAndUpdates()
txCount = txInit.size
stateCount = stateInit.size
Platform.runLater {
logo.opacityProperty().animate(1.0, Duration.seconds(2.5))
transactions.value = txCount.toString()
states.value = stateCount.toString()
}
val fxScheduler = Schedulers.from({ Platform.runLater(it) })
subscriptions.add(txNext.observeOn(fxScheduler).subscribe {
transactions.value = (++txCount).toString()
})
subscriptions.add(stateNext.observeOn(fxScheduler).subscribe {
stateCount += (it.produced.size - it.consumed.size)
states.value = stateCount.toString()
})
} catch (e: Exception) {
log.log(Level.WARNING, "RPC failed: ${e.message}", e)
}
config.state = NodeState.RUNNING
log.info("Node '${config.legalName}' is now ready.")
header.isDisable = false
}
private fun pollCashBalances(ops: CordaRPCOps) {
try { try {
val verifiedTx = ops.verifiedTransactions()
val statesInVault = ops.vaultAndUpdates()
val cashBalances = ops.getCashBalances().entries.joinToString( val cashBalances = ops.getCashBalances().entries.joinToString(
separator = ", ", separator = ", ",
transform = { e -> e.value.toString() } transform = { e -> e.value.toString() }
) )
Platform.runLater { Platform.runLater {
logo.opacityProperty().animate(1.0, Duration.seconds(2.5))
states.value = fetchAndDrop(statesInVault).size.toString()
transactions.value = fetchAndDrop(verifiedTx).size.toString()
balance.value = if (cashBalances.isNullOrEmpty()) "0" else cashBalances balance.value = if (cashBalances.isNullOrEmpty()) "0" else cashBalances
} }
} catch (e: Exception) { } catch (e: Exception) {
log.log(Level.WARNING, "RPC failed: ${e.message}", e) log.log(Level.WARNING, "Cash balance RPC failed: ${e.message}", e)
} }
}) }
fun destroy() { fun destroy() {
if (!isDestroyed) { if (!isDestroyed) {
subscriptions.forEach { it.unsubscribe() }
webServer.close() webServer.close()
explorer.close() explorer.close()
viewer.close() viewer.close()
@ -197,12 +229,6 @@ class NodeTerminalView : Fragment() {
} }
} }
// TODO - Will change when we modify RPC Observables handling.
private fun <T> fetchAndDrop(pair: Pair<T, rx.Observable<*>>): T {
pair.second.notUsed()
return pair.first
}
class TerminalSettingsProvider : DefaultSettingsProvider() { class TerminalSettingsProvider : DefaultSettingsProvider() {
override fun getDefaultStyle() = TextStyle(TerminalColor.WHITE, TerminalColor.rgb(50, 50, 50)) override fun getDefaultStyle() = TextStyle(TerminalColor.WHITE, TerminalColor.rgb(50, 50, 50))
override fun emulateX11CopyPaste() = true override fun emulateX11CopyPaste() = true

View File

@ -4,7 +4,7 @@
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import net.corda.demobench.ui.*?> <?import net.corda.demobench.ui.*?>
<VBox visible="false" prefHeight="953.0" prefWidth="1363.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" styleClass="terminal-vbox"> <VBox visible="false" prefHeight="953.0" prefWidth="1363.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" styleClass="terminal-vbox">
<HBox prefHeight="95.0" prefWidth="800.0" styleClass="header"> <HBox fx:id="header" disable="true" prefHeight="95.0" prefWidth="800.0" styleClass="header">
<VBox prefHeight="66.0" HBox.hgrow="ALWAYS"> <VBox prefHeight="66.0" HBox.hgrow="ALWAYS">
<Label fx:id="nodeName" style="-fx-font-size: 40; -fx-text-fill: red;"/> <Label fx:id="nodeName" style="-fx-font-size: 40; -fx-text-fill: red;"/>
</VBox> </VBox>
@ -13,11 +13,11 @@
<PropertyLabel fx:id="transactions" name="Known transactions: "/> <PropertyLabel fx:id="transactions" name="Known transactions: "/>
<PropertyLabel fx:id="balance" name="Balance: "/> <PropertyLabel fx:id="balance" name="Balance: "/>
</VBox> </VBox>
<Button fx:id="viewDatabaseButton" disable="true" mnemonicParsing="false" prefHeight="92.0" prefWidth="115.0" <Button fx:id="viewDatabaseButton" mnemonicParsing="false" prefHeight="92.0" prefWidth="115.0"
styleClass="big-button" text="View&#10;Database" textAlignment="CENTER"/> styleClass="big-button" text="View&#10;Database" textAlignment="CENTER"/>
<Button fx:id="launchWebButton" disable="true" mnemonicParsing="false" prefHeight="92.0" prefWidth="125.0" <Button fx:id="launchWebButton" mnemonicParsing="false" prefHeight="92.0" prefWidth="125.0"
styleClass="big-button" text="Launch&#10;Web Server" textAlignment="CENTER"/> styleClass="big-button" text="Launch&#10;Web Server" textAlignment="CENTER"/>
<Button fx:id="launchExplorerButton" disable="true" mnemonicParsing="false" prefHeight="92.0" prefWidth="115.0" <Button fx:id="launchExplorerButton" mnemonicParsing="false" prefHeight="92.0" prefWidth="115.0"
styleClass="big-button" text="Launch&#10;Explorer" textAlignment="CENTER"/> styleClass="big-button" text="Launch&#10;Explorer" textAlignment="CENTER"/>
</HBox> </HBox>
<Pane minHeight="8" mouseTransparent="true" styleClass="terminal-gradient-pane"/> <Pane minHeight="8" mouseTransparent="true" styleClass="terminal-gradient-pane"/>