Split off External applications from main library

External applications now accept containers which function as the
frontend where the ExtApplication functions as the backend. Containers
execute within their own engine and have their own context and are
destroyed when released.
This commit is contained in:
obscuren 2014-04-30 01:44:02 +02:00
parent 922974c760
commit 64c2550b31
4 changed files with 287 additions and 58 deletions

175
ethereal/ui/ext_app.go Normal file
View File

@ -0,0 +1,175 @@
package ethui
import (
"fmt"
"github.com/ethereum/eth-go"
"github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethutil"
"github.com/go-qml/qml"
"math/big"
)
type AppContainer interface {
Create() error
Destroy()
Window() *qml.Window
Engine() *qml.Engine
NewBlock(*ethchain.Block)
ObjectChanged(*ethchain.StateObject)
StorageChanged(*ethchain.StateObject, []byte, *big.Int)
}
type ExtApplication struct {
*QEthereum
blockChan chan ethutil.React
changeChan chan ethutil.React
quitChan chan bool
container AppContainer
lib *UiLib
registeredEvents []string
}
func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication {
app := &ExtApplication{
NewQEthereum(lib.eth),
make(chan ethutil.React, 1),
make(chan ethutil.React, 1),
make(chan bool),
container,
lib,
nil,
}
return app
}
func (app *ExtApplication) run() {
// Set the "eth" api on to the containers context
context := app.container.Engine().Context()
context.SetVar("eth", app)
context.SetVar("ui", app.lib)
err := app.container.Create()
if err != nil {
fmt.Println(err)
return
}
// Call the main loop
go app.mainLoop()
// Subscribe to events
reactor := app.lib.eth.Reactor()
reactor.Subscribe("newBlock", app.blockChan)
win := app.container.Window()
win.Show()
win.Wait()
app.stop()
}
func (app *ExtApplication) stop() {
// Clean up
reactor := app.lib.eth.Reactor()
reactor.Unsubscribe("newBlock", app.blockChan)
for _, event := range app.registeredEvents {
reactor.Unsubscribe(event, app.changeChan)
}
// Kill the main loop
app.quitChan <- true
close(app.blockChan)
close(app.quitChan)
close(app.changeChan)
app.container.Destroy()
}
func (app *ExtApplication) mainLoop() {
out:
for {
select {
case <-app.quitChan:
break out
case block := <-app.blockChan:
if block, ok := block.Resource.(*ethchain.Block); ok {
app.container.NewBlock(block)
}
case object := <-app.changeChan:
if stateObject, ok := object.Resource.(*ethchain.StateObject); ok {
app.container.ObjectChanged(stateObject)
} else if _, ok := object.Resource.(*big.Int); ok {
//
}
}
}
}
func (app *ExtApplication) Watch(addr, storageAddr string) {
var event string
if len(storageAddr) == 0 {
event = "storage:" + string(ethutil.FromHex(addr)) + ":" + string(ethutil.FromHex(storageAddr))
app.lib.eth.Reactor().Subscribe(event, app.changeChan)
} else {
event = "object:" + string(ethutil.FromHex(addr))
app.lib.eth.Reactor().Subscribe(event, app.changeChan)
}
app.registeredEvents = append(app.registeredEvents, event)
}
type QEthereum struct {
stateManager *ethchain.StateManager
blockChain *ethchain.BlockChain
txPool *ethchain.TxPool
}
func NewQEthereum(eth *eth.Ethereum) *QEthereum {
return &QEthereum{
eth.StateManager(),
eth.BlockChain(),
eth.TxPool(),
}
}
func (lib *QEthereum) GetBlock(hexHash string) *QBlock {
hash := ethutil.FromHex(hexHash)
block := lib.blockChain.GetBlock(hash)
return &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())}
}
func (lib *QEthereum) GetKey() string {
return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address())
}
func (lib *QEthereum) GetStateObject(address string) *Contract {
stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address))
if stateObject != nil {
return NewContract(stateObject)
}
// See GetStorage for explanation on "nil"
return NewContract(nil)
}
func (lib *QEthereum) Watch(addr, storageAddr string) {
// lib.stateManager.Watch(ethutil.FromHex(addr), ethutil.FromHex(storageAddr))
}
func (lib *QEthereum) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) {
return lib.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr)
}
func (lib *QEthereum) Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) {
return "", nil
}

View File

@ -0,0 +1,73 @@
package ethui
import (
"errors"
"github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethutil"
"github.com/go-qml/qml"
"math/big"
"path/filepath"
)
type HtmlApplication struct {
win *qml.Window
webView qml.Object
engine *qml.Engine
lib *UiLib
path string
}
func NewHtmlApplication(path string, lib *UiLib) *HtmlApplication {
engine := qml.NewEngine()
return &HtmlApplication{engine: engine, lib: lib, path: path}
}
func (app *HtmlApplication) Create() error {
component, err := app.engine.LoadFile(app.lib.AssetPath("qml/webapp.qml"))
if err != nil {
return err
}
if filepath.Ext(app.path) == "eth" {
return errors.New("Ethereum package not yet supported")
// TODO
ethutil.OpenPackage(app.path)
}
win := component.CreateWindow(nil)
win.Set("url", app.path)
webView := win.ObjectByName("webView")
app.win = win
app.webView = webView
return nil
}
func (app *HtmlApplication) Engine() *qml.Engine {
return app.engine
}
func (app *HtmlApplication) Window() *qml.Window {
return app.win
}
func (app *HtmlApplication) NewBlock(block *ethchain.Block) {
b := &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())}
app.webView.Call("onNewBlockCb", b)
}
func (app *HtmlApplication) ObjectChanged(stateObject *ethchain.StateObject) {
app.webView.Call("onObjectChangeCb", NewQStateObject(stateObject))
}
func (app *HtmlApplication) StorageChanged(stateObject *ethchain.StateObject, addr []byte, value *big.Int) {
app.webView.Call("onStorageChangeCb", nil)
}
func (app *HtmlApplication) Destroy() {
app.engine.Destroy()
}

View File

@ -19,9 +19,24 @@ func NewContract(object *ethchain.StateObject) *Contract {
} }
func (c *Contract) GetStorage(address string) string { func (c *Contract) GetStorage(address string) string {
// Because somehow, even if you return nil to QML it
// still has some magical object so we can't rely on
// undefined or null at the QML side
if c.object != nil {
val := c.object.GetMem(ethutil.Big("0x" + address)) val := c.object.GetMem(ethutil.Big("0x" + address))
return val.BigInt().String() return val.BigInt().String()
}
return ""
}
func (c *Contract) Value() string {
if c.object != nil {
return c.object.Amount.String()
}
return ""
} }
type EthLib struct { type EthLib struct {
@ -63,15 +78,23 @@ func (lib *EthLib) GetKey() string {
func (lib *EthLib) GetStateObject(address string) *Contract { func (lib *EthLib) GetStateObject(address string) *Contract {
stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address))
if stateObject != nil {
return NewContract(stateObject) return NewContract(stateObject)
}
// See GetStorage for explanation on "nil"
return NewContract(nil)
} }
func (lib *EthLib) Watch(addr string) { func (lib *EthLib) Watch(addr, storageAddr string) {
lib.stateManager.Watch(ethutil.FromHex(addr)) // lib.stateManager.Watch(ethutil.FromHex(addr), ethutil.FromHex(storageAddr))
} }
func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) {
return lib.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr)
}
func (lib *EthLib) Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) {
var hash []byte var hash []byte
var contractCreation bool var contractCreation bool
if len(recipient) == 0 { if len(recipient) == 0 {

View File

@ -6,9 +6,9 @@ import (
"github.com/ethereum/eth-go" "github.com/ethereum/eth-go"
"github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethutil"
"github.com/obscuren/mutan"
"github.com/ethereum/go-ethereum/utils" "github.com/ethereum/go-ethereum/utils"
"github.com/go-qml/qml" "github.com/go-qml/qml"
"github.com/obscuren/mutan"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -53,60 +53,18 @@ func (ui *UiLib) Open(path string) {
} }
func (ui *UiLib) OpenHtml(path string) { func (ui *UiLib) OpenHtml(path string) {
component, err := ui.engine.LoadFile(ui.AssetPath("qml/webapp.qml")) container := NewHtmlApplication(path, ui)
if err != nil { app := NewExtApplication(container, ui)
ethutil.Config.Log.Debugln(err)
return go app.run()
}
func (ui *UiLib) Watch(addr, storageAddr string) {
if len(storageAddr) == 0 {
ui.eth.Reactor().Subscribe("storage:"+string(ethutil.FromHex(addr))+":"+string(ethutil.FromHex(storageAddr)), nil)
} else {
ui.eth.Reactor().Subscribe("object:"+string(ethutil.FromHex(addr)), nil)
} }
win := component.CreateWindow(nil)
if filepath.Ext(path) == "eth" {
fmt.Println("Ethereum package not yet supported")
return
// TODO
ethutil.OpenPackage(path)
}
win.Set("url", path)
webView := win.ObjectByName("webView")
go func() {
blockChan := make(chan ethutil.React, 1)
addrChan := make(chan ethutil.React, 1)
quitChan := make(chan bool)
go func() {
out:
for {
select {
case <-quitChan:
ui.eth.Reactor().Unsubscribe("newBlock", blockChan)
break out
case block := <-blockChan:
if block, ok := block.Resource.(*ethchain.Block); ok {
b := &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())}
webView.Call("onNewBlockCb", b)
}
case stateObject := <-addrChan:
if stateObject, ok := stateObject.Resource.(*ethchain.StateObject); ok {
webView.Call("onObjectChangeCb", NewQStateObject(stateObject))
}
}
}
// Clean up
close(blockChan)
close(quitChan)
}()
ui.eth.Reactor().Subscribe("newBlock", blockChan)
ui.eth.Reactor().Subscribe("addressChanged", addrChan)
win.Show()
win.Wait()
quitChan <- true
}()
} }
func (ui *UiLib) Muted(content string) { func (ui *UiLib) Muted(content string) {