diff options
author | Felix Lange <fjl@twurst.com> | 2014-10-23 21:01:27 +0800 |
---|---|---|
committer | Felix Lange <fjl@twurst.com> | 2014-10-23 21:01:27 +0800 |
commit | 69baa465ea69ae60eed802445cf0132b9eb69934 (patch) | |
tree | b09da7582b5c4850d4db13aee808f2fef2f97de0 /cmd/mist/gui.go | |
parent | 50fd46924900869e7210217c6a07979b544991c8 (diff) | |
parent | feef194829b07570e91873ed5d1e8cc51e8fa430 (diff) | |
download | go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.tar.gz go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.tar.zst go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.zip |
Merge eth-go repository into go-ethereum
mist, etheruem have been moved to cmd/
Diffstat (limited to 'cmd/mist/gui.go')
-rw-r--r-- | cmd/mist/gui.go | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/cmd/mist/gui.go b/cmd/mist/gui.go new file mode 100644 index 000000000..2a9ab3918 --- /dev/null +++ b/cmd/mist/gui.go @@ -0,0 +1,535 @@ +package main + +import "C" + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "path" + "runtime" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/ethchain" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethlog" + "github.com/ethereum/go-ethereum/ethminer" + "github.com/ethereum/go-ethereum/ethpipe" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/ethwire" + "gopkg.in/qml.v1" +) + +/* +func LoadExtension(path string) (uintptr, error) { + lib, err := ffi.NewLibrary(path) + if err != nil { + return 0, err + } + + so, err := lib.Fct("sharedObject", ffi.Pointer, nil) + if err != nil { + return 0, err + } + + ptr := so() + + err = lib.Close() + if err != nil { + return 0, err + } + + return ptr.Interface().(uintptr), nil +} +*/ + +var logger = ethlog.NewLogger("GUI") + +type Gui struct { + // The main application window + win *qml.Window + // QML Engine + engine *qml.Engine + component *qml.Common + qmlDone bool + // The ethereum interface + eth *eth.Ethereum + + // The public Ethereum library + uiLib *UiLib + + txDb *ethdb.LDBDatabase + + logLevel ethlog.LogLevel + open bool + + pipe *ethpipe.JSPipe + + Session string + clientIdentity *ethwire.SimpleClientIdentity + config *ethutil.ConfigManager + + plugins map[string]plugin + + miner *ethminer.Miner + stdLog ethlog.LogSystem +} + +// Create GUI, but doesn't start it +func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, clientIdentity *ethwire.SimpleClientIdentity, session string, logLevel int) *Gui { + db, err := ethdb.NewLDBDatabase("tx_database") + if err != nil { + panic(err) + } + + pipe := ethpipe.NewJSPipe(ethereum) + gui := &Gui{eth: ethereum, txDb: db, pipe: pipe, logLevel: ethlog.LogLevel(logLevel), Session: session, open: false, clientIdentity: clientIdentity, config: config, plugins: make(map[string]plugin)} + data, _ := ethutil.ReadAllFile(path.Join(ethutil.Config.ExecPath, "plugins.json")) + json.Unmarshal([]byte(data), &gui.plugins) + + return gui +} + +func (gui *Gui) Start(assetPath string) { + + defer gui.txDb.Close() + + // Register ethereum functions + qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ + Init: func(p *ethpipe.JSBlock, obj qml.Object) { p.Number = 0; p.Hash = "" }, + }, { + Init: func(p *ethpipe.JSTransaction, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, + }, { + Init: func(p *ethpipe.KeyVal, obj qml.Object) { p.Key = ""; p.Value = "" }, + }}) + // Create a new QML engine + gui.engine = qml.NewEngine() + context := gui.engine.Context() + gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath) + + // Expose the eth library and the ui library to QML + context.SetVar("gui", gui) + context.SetVar("eth", gui.uiLib) + + /* + vec, errr := LoadExtension("/Users/jeffrey/Desktop/build-libqmltest-Desktop_Qt_5_2_1_clang_64bit-Debug/liblibqmltest_debug.dylib") + fmt.Printf("Fetched vec with addr: %#x\n", vec) + if errr != nil { + fmt.Println(errr) + } else { + context.SetVar("vec", (unsafe.Pointer)(vec)) + } + */ + + // 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 (but do not save loglevel) + gui.logLevel = ethlog.Silence + gui.open = false +} + +func (gui *Gui) Stop() { + if gui.open { + gui.logLevel = ethlog.Silence + gui.open = false + gui.win.Hide() + } + + gui.uiLib.jsEngine.Stop() + + logger.Infoln("Stopped") +} + +func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { + component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/main.qml")) + if err != nil { + return nil, err + } + + gui.win = gui.createWindow(component) + + gui.update() + + return gui.win, nil +} + +// The done handler will be called by QML when all views have been loaded +func (gui *Gui) Done() { + gui.qmlDone = true +} + +func (gui *Gui) ImportKey(filePath string) { +} + +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) setInitialChainManager() { + sBlk := gui.eth.ChainManager().LastBlockHash + blk := gui.eth.ChainManager().GetBlock(sBlk) + for ; blk != nil; blk = gui.eth.ChainManager().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 +} + +func (gui *Gui) loadAddressBook() { + view := gui.getObjectByName("infoView") + view.Call("clearAddress") + + nameReg := gui.pipe.World().Config().Get("NameReg") + if nameReg != nil { + nameReg.EachStorage(func(name string, value *ethutil.Value) { + if name[0] != 0 { + value.Decode() + + view.Call("addAddress", struct{ Name, Address string }{name, ethutil.Bytes2Hex(value.Bytes())}) + } + }) + } +} + +func (gui *Gui) insertTransaction(window string, tx *ethchain.Transaction) { + pipe := ethpipe.New(gui.eth) + nameReg := pipe.World().Config().Get("NameReg") + addr := gui.address() + + var inout string + if bytes.Compare(tx.Sender(), addr) == 0 { + inout = "send" + } else { + inout = "recv" + } + + var ( + ptx = ethpipe.NewJSTx(tx, pipe.World().State()) + send = nameReg.Storage(tx.Sender()) + rec = nameReg.Storage(tx.Recipient) + s, r string + ) + + if tx.CreatesContract() { + rec = nameReg.Storage(tx.CreationAddress(pipe.World().State())) + } + + if send.Len() != 0 { + s = strings.Trim(send.Str(), "\x00") + } else { + s = ethutil.Bytes2Hex(tx.Sender()) + } + if rec.Len() != 0 { + r = strings.Trim(rec.Str(), "\x00") + } else { + if tx.CreatesContract() { + r = ethutil.Bytes2Hex(tx.CreationAddress(pipe.World().State())) + } else { + r = ethutil.Bytes2Hex(tx.Recipient) + } + } + ptx.Sender = s + ptx.Address = r + + if window == "post" { + //gui.getObjectByName("transactionView").Call("addTx", ptx, inout) + } else { + gui.getObjectByName("pendingTxView").Call("addTx", ptx, inout) + } +} + +func (gui *Gui) readPreviousTransactions() { + it := gui.txDb.Db().NewIterator(nil, nil) + for it.Next() { + tx := ethchain.NewTransactionFromBytes(it.Value()) + + gui.insertTransaction("post", tx) + + } + it.Release() +} + +func (gui *Gui) processBlock(block *ethchain.Block, initial bool) { + name := strings.Trim(gui.pipe.World().Config().Get("NameReg").Storage(block.Coinbase).Str(), "\x00") + b := ethpipe.NewJSBlock(block) + b.Name = name + + gui.getObjectByName("chainView").Call("addBlock", b, 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) +} + +func (self *Gui) getObjectByName(objectName string) qml.Object { + return self.win.Root().ObjectByName(objectName) +} + +// Simple go routine function that updates the list of peers in the GUI +func (gui *Gui) update() { + // We have to wait for qml to be done loading all the windows. + for !gui.qmlDone { + time.Sleep(500 * time.Millisecond) + } + + go func() { + go gui.setInitialChainManager() + gui.loadAddressBook() + gui.setPeerInfo() + gui.readPreviousTransactions() + }() + + for _, plugin := range gui.plugins { + logger.Infoln("Loading plugin ", plugin.Name) + + gui.win.Root().Call("addPlugin", plugin.Path, "") + } + + peerUpdateTicker := time.NewTicker(5 * time.Second) + generalUpdateTicker := time.NewTicker(500 * time.Millisecond) + statsUpdateTicker := 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()).Balance()))) + + lastBlockLabel := gui.getObjectByName("lastBlockLabel") + miningLabel := gui.getObjectByName("miningLabel") + + events := gui.eth.EventMux().Subscribe( + eth.ChainSyncEvent{}, + eth.PeerListEvent{}, + ethchain.NewBlockEvent{}, + ethchain.TxEvent{}, + ethminer.Event{}, + ) + + // nameReg := gui.pipe.World().Config().Get("NameReg") + // mux.Subscribe("object:"+string(nameReg.Address()), objectChan) + + go func() { + defer events.Unsubscribe() + for { + select { + case ev, isopen := <-events.Chan(): + if !isopen { + return + } + switch ev := ev.(type) { + case ethchain.NewBlockEvent: + gui.processBlock(ev.Block, false) + if bytes.Compare(ev.Block.Coinbase, gui.address()) == 0 { + gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.address()).Balance(), nil) + } + + case ethchain.TxEvent: + tx := ev.Tx + if ev.Type == ethchain.TxPre { + object := state.GetAccount(gui.address()) + + if bytes.Compare(tx.Sender(), gui.address()) == 0 { + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) + } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { + unconfirmedFunds.Add(unconfirmedFunds, tx.Value) + } + + gui.setWalletValue(object.Balance(), unconfirmedFunds) + + gui.insertTransaction("pre", tx) + + } else if ev.Type == ethchain.TxPost { + object := state.GetAccount(gui.address()) + if bytes.Compare(tx.Sender(), gui.address()) == 0 { + object.SubAmount(tx.Value) + + //gui.getObjectByName("transactionView").Call("addTx", ethpipe.NewJSTx(tx), "send") + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { + object.AddAmount(tx.Value) + + //gui.getObjectByName("transactionView").Call("addTx", ethpipe.NewJSTx(tx), "recv") + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } + + gui.setWalletValue(object.Balance(), nil) + + state.UpdateStateObject(object) + } + + // case object: + // gui.loadAddressBook() + + case eth.PeerListEvent: + gui.setPeerInfo() + + case ethminer.Event: + if ev.Type == ethminer.Started { + gui.miner = ev.Miner + } else { + gui.miner = nil + } + } + + case <-peerUpdateTicker.C: + gui.setPeerInfo() + case <-generalUpdateTicker.C: + statusText := "#" + gui.eth.ChainManager().CurrentBlock.Number.String() + lastBlockLabel.Set("text", statusText) + + if gui.miner != nil { + pow := gui.miner.GetPow() + miningLabel.Set("text", "Mining @ "+strconv.FormatInt(pow.GetHashrate(), 10)+"Khash") + } + + blockLength := gui.eth.BlockPool().BlocksProcessed + chainLength := gui.eth.BlockPool().ChainLength + + var ( + pct float64 = 1.0 / float64(chainLength) * float64(blockLength) + dlWidget = gui.win.Root().ObjectByName("downloadIndicator") + dlLabel = gui.win.Root().ObjectByName("downloadLabel") + ) + + dlWidget.Set("value", pct) + dlLabel.Set("text", fmt.Sprintf("%d / %d", blockLength, chainLength)) + + case <-statsUpdateTicker.C: + gui.setStatsPane() + } + } + }() +} + +func (gui *Gui) setStatsPane() { + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + + statsPane := gui.getObjectByName("statsPane") + statsPane.Set("text", fmt.Sprintf(`###### Mist 0.6.8 (%s) ####### + +eth %d (p2p = %d) + +CPU: # %d +Goroutines: # %d +CGoCalls: # %d + +Alloc: %d +Heap Alloc: %d + +CGNext: %x +NumGC: %d +`, runtime.Version(), + eth.ProtocolVersion, eth.P2PVersion, + runtime.NumCPU, runtime.NumGoroutine(), runtime.NumCgoCall(), + memStats.Alloc, memStats.HeapAlloc, + memStats.NextGC, memStats.NumGC, + )) +} + +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.pipe.Peers() { + 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() +} |