diff options
author | obscuren <geffobscura@gmail.com> | 2014-07-01 19:45:39 +0800 |
---|---|---|
committer | obscuren <geffobscura@gmail.com> | 2014-07-01 19:45:39 +0800 |
commit | 253c23240b8cec56e2bb21072291e2f7ef1a49e9 (patch) | |
tree | 64503d09f1120ef2327a8184e46d5ee8bc7090bd /ethereal/ui | |
parent | 0ce9003ba77c0552c9058caa55d2fea6711ac18c (diff) | |
parent | 098f7f23ce62d3f0c60d30d325576de93795cc4b (diff) | |
download | dexon-253c23240b8cec56e2bb21072291e2f7ef1a49e9.tar dexon-253c23240b8cec56e2bb21072291e2f7ef1a49e9.tar.gz dexon-253c23240b8cec56e2bb21072291e2f7ef1a49e9.tar.bz2 dexon-253c23240b8cec56e2bb21072291e2f7ef1a49e9.tar.lz dexon-253c23240b8cec56e2bb21072291e2f7ef1a49e9.tar.xz dexon-253c23240b8cec56e2bb21072291e2f7ef1a49e9.tar.zst dexon-253c23240b8cec56e2bb21072291e2f7ef1a49e9.zip |
Merge branch 'feature/keys' of https://github.com/ethersphere/go-ethereum into ethersphere-feature/keys
Conflicts:
.gitignore
README.md
Diffstat (limited to 'ethereal/ui')
-rw-r--r-- | ethereal/ui/debugger.go | 234 | ||||
-rw-r--r-- | ethereal/ui/ext_app.go | 132 | ||||
-rw-r--r-- | ethereal/ui/gui.go | 405 | ||||
-rw-r--r-- | ethereal/ui/html_container.go | 133 | ||||
-rw-r--r-- | ethereal/ui/qml_app.go | 59 | ||||
-rw-r--r-- | ethereal/ui/ui_lib.go | 100 |
6 files changed, 1063 insertions, 0 deletions
diff --git a/ethereal/ui/debugger.go b/ethereal/ui/debugger.go new file mode 100644 index 000000000..5ad1b4a2c --- /dev/null +++ b/ethereal/ui/debugger.go @@ -0,0 +1,234 @@ +package ethui + +import ( + "fmt" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" + "math/big" + "strings" +) + +type DebuggerWindow struct { + win *qml.Window + engine *qml.Engine + lib *UiLib + Db *Debugger +} + +func NewDebuggerWindow(lib *UiLib) *DebuggerWindow { + engine := qml.NewEngine() + component, err := engine.LoadFile(lib.AssetPath("debugger/debugger.qml")) + if err != nil { + fmt.Println(err) + + return nil + } + + win := component.CreateWindow(nil) + db := &Debugger{win, make(chan bool), make(chan bool), true, false, true} + + return &DebuggerWindow{engine: engine, win: win, lib: lib, Db: db} +} + +func (self *DebuggerWindow) Show() { + context := self.engine.Context() + context.SetVar("dbg", self) + + go func() { + self.win.Show() + self.win.Wait() + }() +} + +func (self *DebuggerWindow) SetCode(code string) { + self.win.Set("codeText", code) +} + +func (self *DebuggerWindow) SetData(data string) { + self.win.Set("dataText", data) +} +func (self *DebuggerWindow) SetAsm(data string) { + dis := ethchain.Disassemble(ethutil.Hex2Bytes(data)) + for _, str := range dis { + self.win.Root().Call("setAsm", str) + } +} + +func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, dataStr string) { + if !self.Db.done { + self.Db.Q <- true + } + self.Db.breakOnInstr = self.win.Root().ObjectByName("breakEachLine").Bool("checked") + + defer func() { + if r := recover(); r != nil { + self.Logf("compile FAULT: %v", r) + } + }() + + data := ethutil.StringToByteFunc(dataStr, func(s string) (ret []byte) { + slice := strings.Split(dataStr, "\n") + for _, dataItem := range slice { + d := ethutil.FormatData(dataItem) + ret = append(ret, d...) + } + return + }) + + var err error + script := ethutil.StringToByteFunc(scriptStr, func(s string) (ret []byte) { + ret, err = ethutil.Compile(s) + return + }) + + if err != nil { + self.Logln(err) + + return + } + + dis := ethchain.Disassemble(script) + self.win.Root().Call("clearAsm") + self.win.Root().Call("clearLog") + + for _, str := range dis { + self.win.Root().Call("setAsm", str) + } + + var ( + gas = ethutil.Big(gasStr) + gasPrice = ethutil.Big(gasPriceStr) + value = ethutil.Big(valueStr) + // Contract addr as test address + keyPair = self.lib.eth.KeyManager().KeyPair() + callerTx = ethchain.NewContractCreationTx(ethutil.Big(valueStr), gas, gasPrice, script) + ) + callerTx.Sign(keyPair.PrivateKey) + + state := self.lib.eth.BlockChain().CurrentBlock.State() + account := self.lib.eth.StateManager().TransState().GetAccount(keyPair.Address()) + contract := ethchain.MakeContract(callerTx, state) + contract.Amount = value + callerClosure := ethchain.NewClosure(account, contract, script, state, gas, gasPrice) + + block := self.lib.eth.BlockChain().CurrentBlock + vm := ethchain.NewVm(state, self.lib.eth.StateManager(), ethchain.RuntimeVars{ + Block: block, + Origin: account.Address(), + BlockNumber: block.Number, + PrevHash: block.PrevHash, + Coinbase: block.Coinbase, + Time: block.Time, + Diff: block.Difficulty, + Value: ethutil.Big(valueStr), + }) + vm.Verbose = true + + self.Db.done = false + self.Logf("callsize %d", len(script)) + go func() { + ret, g, err := callerClosure.Call(vm, data, self.Db.halting) + tot := new(big.Int).Mul(g, gasPrice) + self.Logf("gas usage %v total price = %v (%v)", g, tot, ethutil.CurrencyToString(tot)) + if err != nil { + self.Logln("exited with errors:", err) + } else { + if len(ret) > 0 { + self.Logf("exited: % x", ret) + } else { + self.Logf("exited: nil") + } + } + + state.Reset() + + if !self.Db.interrupt { + self.Db.done = true + } else { + self.Db.interrupt = false + } + }() +} + +func (self *DebuggerWindow) Logf(format string, v ...interface{}) { + self.win.Root().Call("setLog", fmt.Sprintf(format, v...)) +} + +func (self *DebuggerWindow) Logln(v ...interface{}) { + str := fmt.Sprintln(v...) + self.Logf("%s", str[:len(str)-1]) +} + +func (self *DebuggerWindow) Next() { + self.Db.Next() +} + +type Debugger struct { + win *qml.Window + N chan bool + Q chan bool + done, interrupt bool + breakOnInstr bool +} + +type storeVal struct { + Key, Value string +} + +func (d *Debugger) halting(pc int, op ethchain.OpCode, mem *ethchain.Memory, stack *ethchain.Stack, stateObject *ethchain.StateObject) bool { + d.win.Root().Call("setInstruction", pc) + d.win.Root().Call("clearMem") + d.win.Root().Call("clearStack") + d.win.Root().Call("clearStorage") + + addr := 0 + for i := 0; i+32 <= mem.Len(); i += 32 { + d.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("% x", mem.Data()[i:i+32])}) + addr++ + } + + for _, val := range stack.Data() { + d.win.Root().Call("setStack", val.String()) + } + + stateObject.State().EachStorage(func(key string, node *ethutil.Value) { + d.win.Root().Call("setStorage", storeVal{fmt.Sprintf("% x", key), fmt.Sprintf("% x", node.Str())}) + }) + + if d.breakOnInstr { + out: + for { + select { + case <-d.N: + break out + case <-d.Q: + d.interrupt = true + d.clearBuffers() + + return false + } + } + } + + return true +} + +func (d *Debugger) clearBuffers() { +out: + // drain + for { + select { + case <-d.N: + case <-d.Q: + default: + break out + } + } +} + +func (d *Debugger) Next() { + if !d.done { + d.N <- true + } +} diff --git a/ethereal/ui/ext_app.go b/ethereal/ui/ext_app.go new file mode 100644 index 000000000..0230c46ab --- /dev/null +++ b/ethereal/ui/ext_app.go @@ -0,0 +1,132 @@ +package ethui + +import ( + "fmt" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" +) + +type AppContainer interface { + Create() error + Destroy() + + Window() *qml.Window + Engine() *qml.Engine + + NewBlock(*ethchain.Block) + ObjectChanged(*ethchain.StateObject) + StorageChanged(*ethchain.StorageState) + NewWatcher(chan bool) +} + +type ExtApplication struct { + *ethpub.PEthereum + + blockChan chan ethutil.React + changeChan chan ethutil.React + quitChan chan bool + watcherQuitChan chan bool + + container AppContainer + lib *UiLib + registeredEvents []string +} + +func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication { + app := &ExtApplication{ + ethpub.NewPEthereum(lib.eth), + make(chan ethutil.React, 1), + make(chan ethutil.React, 1), + make(chan bool), + 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) + + app.container.NewWatcher(app.watcherQuitChan) + + 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 + app.watcherQuitChan <- 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 storageObject, ok := object.Resource.(*ethchain.StorageState); ok { + app.container.StorageChanged(storageObject) + } + } + } + +} + +func (app *ExtApplication) Watch(addr, storageAddr string) { + var event string + if len(storageAddr) == 0 { + event = "object:" + string(ethutil.Hex2Bytes(addr)) + app.lib.eth.Reactor().Subscribe(event, app.changeChan) + } else { + event = "storage:" + string(ethutil.Hex2Bytes(addr)) + ":" + string(ethutil.Hex2Bytes(storageAddr)) + app.lib.eth.Reactor().Subscribe(event, app.changeChan) + } + + app.registeredEvents = append(app.registeredEvents, event) +} diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go new file mode 100644 index 000000000..d8c39e837 --- /dev/null +++ b/ethereal/ui/gui.go @@ -0,0 +1,405 @@ +package ethui + +import ( + "bytes" + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethdb" + "github.com/ethereum/eth-go/ethlog" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" + "github.com/go-qml/qml" + "math/big" + "strings" + "time" +) + +var logger = ethlog.NewLogger("GUI") + +type Gui struct { + // The main application window + win *qml.Window + // QML Engine + engine *qml.Engine + component *qml.Common + // The ethereum interface + eth *eth.Ethereum + + // The public Ethereum library + uiLib *UiLib + + txDb *ethdb.LDBDatabase + + pub *ethpub.PEthereum + logLevel ethlog.LogLevel + open bool + + Session string +} + +// Create GUI, but doesn't start it +func New(ethereum *eth.Ethereum, session string, logLevel int) *Gui { + + db, err := ethdb.NewLDBDatabase("tx_database") + if err != nil { + panic(err) + } + + pub := ethpub.NewPEthereum(ethereum) + + return &Gui{eth: ethereum, txDb: db, pub: pub, logLevel: ethlog.LogLevel(logLevel), Session: session, open: false} +} + +func (gui *Gui) Start(assetPath string) { + const version = "0.5.0 RC15" + + defer gui.txDb.Close() + + // Register ethereum functions + qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ + Init: func(p *ethpub.PBlock, obj qml.Object) { p.Number = 0; p.Hash = "" }, + }, { + Init: func(p *ethpub.PTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, + }, { + Init: func(p *ethpub.KeyVal, obj qml.Object) { p.Key = ""; p.Value = "" }, + }}) + + ethutil.Config.SetClientString("Ethereal") + + // Create a new QML engine + gui.engine = qml.NewEngine() + context := gui.engine.Context() + + // Expose the eth library and the ui library to QML + context.SetVar("eth", gui) + context.SetVar("pub", gui.pub) + gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath) + context.SetVar("ui", gui.uiLib) + + // Load the main QML interface + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + + var win *qml.Window + var err error + var addlog = false + if len(data) == 0 { + win, err = gui.showKeyImport(context) + } else { + win, err = gui.showWallet(context) + addlog = true + } + if err != nil { + logger.Errorln("asset not found: you can set an alternative asset path on the command line using option 'asset_path'", err) + + panic(err) + } + + logger.Infoln("Starting GUI") + gui.open = true + win.Show() + // only add the gui logger after window is shown otherwise slider wont be shown + if addlog { + ethlog.AddLogSystem(gui) + } + win.Wait() + // need to silence gui logger after window closed otherwise logsystem hangs + gui.SetLogLevel(ethlog.Silence) + gui.open = false +} + +func (gui *Gui) Stop() { + if gui.open { + gui.SetLogLevel(ethlog.Silence) + gui.open = false + gui.win.Hide() + } + logger.Infoln("Stopped") +} + +func (gui *Gui) ToggleMining() { + var txt string + if gui.eth.Mining { + utils.StopMining(gui.eth) + txt = "Start mining" + } else { + utils.StartMining(gui.eth) + txt = "Stop mining" + } + + gui.win.Root().Set("miningButtonText", txt) +} + +func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { + component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/wallet.qml")) + if err != nil { + return nil, err + } + + win := gui.createWindow(component) + + gui.setInitialBlockChain() + gui.loadAddressBook() + gui.readPreviousTransactions() + gui.setPeerInfo() + + go gui.update() + + return win, nil +} + +func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) { + context.SetVar("lib", gui) + component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml")) + if err != nil { + return nil, err + } + return gui.createWindow(component), nil +} + +func (gui *Gui) createWindow(comp qml.Object) *qml.Window { + win := comp.CreateWindow(nil) + + gui.win = win + gui.uiLib.win = win + + return gui.win +} + +func (gui *Gui) ImportAndSetPrivKey(secret string) bool { + err := gui.eth.KeyManager().InitFromString(gui.Session, 0, secret) + if err != nil { + logger.Errorln("unable to import: ", err) + return false + } + logger.Errorln("successfully imported: ", err) + return true +} + +func (gui *Gui) CreateAndSetPrivKey() (string, string, string, string) { + err := gui.eth.KeyManager().Init(gui.Session, 0, true) + if err != nil { + logger.Errorln("unable to create key: ", err) + return "", "", "", "" + } + return gui.eth.KeyManager().KeyPair().AsStrings() +} + +func (gui *Gui) setInitialBlockChain() { + sBlk := gui.eth.BlockChain().LastBlockHash + blk := gui.eth.BlockChain().GetBlock(sBlk) + for ; blk != nil; blk = gui.eth.BlockChain().GetBlock(sBlk) { + sBlk = blk.PrevHash + addr := gui.address() + + // Loop through all transactions to see if we missed any while being offline + for _, tx := range blk.Transactions() { + if bytes.Compare(tx.Sender(), addr) == 0 || bytes.Compare(tx.Recipient, addr) == 0 { + if ok, _ := gui.txDb.Get(tx.Hash()); ok == nil { + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } + + } + } + + gui.processBlock(blk, true) + } +} + +type address struct { + Name, Address string +} + +var namereg = ethutil.Hex2Bytes("bb5f186604d057c1c5240ca2ae0f6430138ac010") + +func (gui *Gui) loadAddressBook() { + gui.win.Root().Call("clearAddress") + stateObject := gui.eth.StateManager().CurrentState().GetStateObject(namereg) + if stateObject != nil { + stateObject.State().EachStorage(func(name string, value *ethutil.Value) { + gui.win.Root().Call("addAddress", struct{ Name, Address string }{name, ethutil.Bytes2Hex(value.Bytes())}) + }) + } +} + +func (gui *Gui) readPreviousTransactions() { + it := gui.txDb.Db().NewIterator(nil, nil) + addr := gui.address() + for it.Next() { + tx := ethchain.NewTransactionFromBytes(it.Value()) + + var inout string + if bytes.Compare(tx.Sender(), addr) == 0 { + inout = "send" + } else { + inout = "recv" + } + + gui.win.Root().Call("addTx", ethpub.NewPTx(tx), inout) + + } + it.Release() +} + +func (gui *Gui) processBlock(block *ethchain.Block, initial bool) { + gui.win.Root().Call("addBlock", ethpub.NewPBlock(block), initial) +} + +func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) { + var str string + if unconfirmedFunds != nil { + pos := "+" + if unconfirmedFunds.Cmp(big.NewInt(0)) < 0 { + pos = "-" + } + val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds))) + str = fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(amount), pos, val) + } else { + str = fmt.Sprintf("%v", ethutil.CurrencyToString(amount)) + } + + gui.win.Root().Call("setWalletValue", str) +} + +// Simple go routine function that updates the list of peers in the GUI +func (gui *Gui) update() { + reactor := gui.eth.Reactor() + + blockChan := make(chan ethutil.React, 1) + txChan := make(chan ethutil.React, 1) + objectChan := make(chan ethutil.React, 1) + peerChan := make(chan ethutil.React, 1) + + reactor.Subscribe("newBlock", blockChan) + reactor.Subscribe("newTx:pre", txChan) + reactor.Subscribe("newTx:post", txChan) + reactor.Subscribe("object:"+string(namereg), objectChan) + reactor.Subscribe("peerList", peerChan) + + ticker := time.NewTicker(5 * time.Second) + + state := gui.eth.StateManager().TransState() + + unconfirmedFunds := new(big.Int) + gui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(state.GetAccount(gui.address()).Amount))) + + for { + select { + case b := <-blockChan: + block := b.Resource.(*ethchain.Block) + gui.processBlock(block, false) + if bytes.Compare(block.Coinbase, gui.address()) == 0 { + gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.address()).Amount, nil) + } + + case txMsg := <-txChan: + tx := txMsg.Resource.(*ethchain.Transaction) + + if txMsg.Event == "newTx:pre" { + object := state.GetAccount(gui.address()) + + if bytes.Compare(tx.Sender(), gui.address()) == 0 { + gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "send") + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) + } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { + gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "recv") + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + unconfirmedFunds.Add(unconfirmedFunds, tx.Value) + } + + gui.setWalletValue(object.Amount, unconfirmedFunds) + } else { + object := state.GetAccount(gui.address()) + if bytes.Compare(tx.Sender(), gui.address()) == 0 { + object.SubAmount(tx.Value) + } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { + object.AddAmount(tx.Value) + } + + gui.setWalletValue(object.Amount, nil) + + state.UpdateStateObject(object) + } + case <-objectChan: + gui.loadAddressBook() + case <-peerChan: + gui.setPeerInfo() + case <-ticker.C: + gui.setPeerInfo() + } + } +} + +func (gui *Gui) setPeerInfo() { + gui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", gui.eth.PeerCount(), gui.eth.MaxPeers)) + + gui.win.Root().Call("resetPeers") + for _, peer := range gui.pub.GetPeers() { + gui.win.Root().Call("addPeer", peer) + } +} + +func (gui *Gui) privateKey() string { + return ethutil.Bytes2Hex(gui.eth.KeyManager().PrivateKey()) +} + +func (gui *Gui) address() []byte { + return gui.eth.KeyManager().Address() +} + +func (gui *Gui) RegisterName(name string) { + name = fmt.Sprintf("\"%s\"\n1", name) + gui.pub.Transact(gui.privateKey(), "namereg", "1000", "1000000", "150", name) +} + +func (gui *Gui) Transact(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) { + return gui.pub.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data) +} + +func (gui *Gui) Create(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) { + return gui.pub.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data) +} + +func (gui *Gui) ChangeClientId(id string) { + ethutil.Config.SetIdentifier(id) +} + +func (gui *Gui) ClientId() string { + return ethutil.Config.Identifier +} + +// functions that allow Gui to implement interface ethlog.LogSystem +func (gui *Gui) SetLogLevel(level ethlog.LogLevel) { + gui.logLevel = level +} + +func (gui *Gui) GetLogLevel() ethlog.LogLevel { + return gui.logLevel +} + +// this extra function needed to give int typecast value to gui widget +// that sets initial loglevel to default +func (gui *Gui) GetLogLevelInt() int { + return int(gui.logLevel) +} + +func (gui *Gui) Println(v ...interface{}) { + gui.printLog(fmt.Sprintln(v...)) +} + +func (gui *Gui) Printf(format string, v ...interface{}) { + gui.printLog(fmt.Sprintf(format, v...)) +} + +// Print function that logs directly to the GUI +func (gui *Gui) printLog(s string) { + str := strings.TrimRight(s, "\n") + lines := strings.Split(str, "\n") + for _, line := range lines { + gui.win.Root().Call("addLog", line) + } +} diff --git a/ethereal/ui/html_container.go b/ethereal/ui/html_container.go new file mode 100644 index 000000000..f2ebd840c --- /dev/null +++ b/ethereal/ui/html_container.go @@ -0,0 +1,133 @@ +package ethui + +import ( + "errors" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" + "github.com/howeyc/fsnotify" + "io/ioutil" + "log" + "net/url" + "os" + "path" + "path/filepath" +) + +type HtmlApplication struct { + win *qml.Window + webView qml.Object + engine *qml.Engine + lib *UiLib + path string + watcher *fsnotify.Watcher +} + +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) RootFolder() string { + folder, err := url.Parse(app.path) + if err != nil { + return "" + } + return path.Dir(folder.RequestURI()) +} +func (app *HtmlApplication) RecursiveFolders() []os.FileInfo { + files, _ := ioutil.ReadDir(app.RootFolder()) + var folders []os.FileInfo + for _, file := range files { + if file.IsDir() { + folders = append(folders, file) + } + } + return folders +} + +func (app *HtmlApplication) NewWatcher(quitChan chan bool) { + var err error + + app.watcher, err = fsnotify.NewWatcher() + if err != nil { + return + } + err = app.watcher.Watch(app.RootFolder()) + if err != nil { + log.Fatal(err) + } + for _, folder := range app.RecursiveFolders() { + fullPath := app.RootFolder() + "/" + folder.Name() + app.watcher.Watch(fullPath) + } + + go func() { + out: + for { + select { + case <-quitChan: + app.watcher.Close() + break out + case <-app.watcher.Event: + //logger.Debugln("Got event:", ev) + app.webView.Call("reload") + case err := <-app.watcher.Error: + // TODO: Do something here + logger.Infoln("Watcher error:", err) + } + } + }() + +} + +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 := ðpub.PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Bytes2Hex(block.Hash())} + app.webView.Call("onNewBlockCb", b) +} + +func (app *HtmlApplication) ObjectChanged(stateObject *ethchain.StateObject) { + app.webView.Call("onObjectChangeCb", ethpub.NewPStateObject(stateObject)) +} + +func (app *HtmlApplication) StorageChanged(storageObject *ethchain.StorageState) { + app.webView.Call("onStorageChangeCb", ethpub.NewPStorageState(storageObject)) +} + +func (app *HtmlApplication) Destroy() { + app.engine.Destroy() +} diff --git a/ethereal/ui/qml_app.go b/ethereal/ui/qml_app.go new file mode 100644 index 000000000..d23fdd110 --- /dev/null +++ b/ethereal/ui/qml_app.go @@ -0,0 +1,59 @@ +package ethui + +import ( + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" +) + +type QmlApplication struct { + win *qml.Window + engine *qml.Engine + lib *UiLib + path string +} + +func NewQmlApplication(path string, lib *UiLib) *QmlApplication { + engine := qml.NewEngine() + return &QmlApplication{engine: engine, path: path, lib: lib} +} + +func (app *QmlApplication) Create() error { + component, err := app.engine.LoadFile(app.path) + if err != nil { + logger.Warnln(err) + } + app.win = component.CreateWindow(nil) + + return nil +} + +func (app *QmlApplication) Destroy() { + app.engine.Destroy() +} + +func (app *QmlApplication) NewWatcher(quitChan chan bool) { +} + +// Events +func (app *QmlApplication) NewBlock(block *ethchain.Block) { + pblock := ðpub.PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Bytes2Hex(block.Hash())} + app.win.Call("onNewBlockCb", pblock) +} + +func (app *QmlApplication) ObjectChanged(stateObject *ethchain.StateObject) { + app.win.Call("onObjectChangeCb", ethpub.NewPStateObject(stateObject)) +} + +func (app *QmlApplication) StorageChanged(storageObject *ethchain.StorageState) { + app.win.Call("onStorageChangeCb", ethpub.NewPStorageState(storageObject)) +} + +// Getters +func (app *QmlApplication) Engine() *qml.Engine { + return app.engine +} +func (app *QmlApplication) Window() *qml.Window { + return app.win +} diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go new file mode 100644 index 000000000..892c1f065 --- /dev/null +++ b/ethereal/ui/ui_lib.go @@ -0,0 +1,100 @@ +package ethui + +import ( + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" + "path" +) + +type memAddr struct { + Num string + Value string +} + +// UI Library that has some basic functionality exposed +type UiLib struct { + engine *qml.Engine + eth *eth.Ethereum + connected bool + assetPath string + // The main application window + win *qml.Window + Db *Debugger + DbWindow *DebuggerWindow +} + +func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { + return &UiLib{engine: engine, eth: eth, assetPath: assetPath} +} + +func (ui *UiLib) OpenQml(path string) { + container := NewQmlApplication(path[7:], ui) + app := NewExtApplication(container, ui) + + go app.run() +} + +func (ui *UiLib) OpenHtml(path string) { + container := NewHtmlApplication(path, ui) + app := NewExtApplication(container, ui) + + go app.run() +} + +func (ui *UiLib) Muted(content string) { + component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml")) + if err != nil { + logger.Debugln(err) + + return + } + win := component.CreateWindow(nil) + go func() { + path := "file://" + ui.AssetPath("muted/index.html") + win.Set("url", path) + + win.Show() + win.Wait() + }() +} + +func (ui *UiLib) Connect(button qml.Object) { + if !ui.connected { + ui.eth.Start(true) + ui.connected = true + button.Set("enabled", false) + } +} + +func (ui *UiLib) ConnectToPeer(addr string) { + ui.eth.ConnectToPeer(addr) +} + +func (ui *UiLib) AssetPath(p string) string { + return path.Join(ui.assetPath, p) +} + +func (self *UiLib) StartDbWithContractAndData(contractHash, data string) { + dbWindow := NewDebuggerWindow(self) + object := self.eth.StateManager().CurrentState().GetStateObject(ethutil.Hex2Bytes(contractHash)) + if len(object.Script()) > 0 { + dbWindow.SetCode("0x" + ethutil.Bytes2Hex(object.Script())) + } + dbWindow.SetData("0x" + data) + + dbWindow.Show() +} + +func (self *UiLib) StartDbWithCode(code string) { + dbWindow := NewDebuggerWindow(self) + dbWindow.SetCode("0x" + code) + dbWindow.Show() +} + +func (self *UiLib) StartDebugger() { + dbWindow := NewDebuggerWindow(self) + //self.DbWindow = dbWindow + + dbWindow.Show() +} |