diff options
Diffstat (limited to 'ethereal/ui')
-rw-r--r-- | ethereal/ui/gui.go | 218 | ||||
-rw-r--r-- | ethereal/ui/library.go | 60 | ||||
-rw-r--r-- | ethereal/ui/ui_lib.go | 75 |
3 files changed, 353 insertions, 0 deletions
diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go new file mode 100644 index 000000000..c8f4bedab --- /dev/null +++ b/ethereal/ui/gui.go @@ -0,0 +1,218 @@ +package ethui + +import ( + "bytes" + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethdb" + "github.com/ethereum/eth-go/ethutil" + "github.com/niemeyer/qml" + "math/big" + "strings" +) + +// Block interface exposed to QML +type Block struct { + Number int + Hash string +} + +type Tx struct { + Value, Hash, Address string +} + +func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { + hash := hex.EncodeToString(tx.Hash()) + sender := hex.EncodeToString(tx.Recipient) + + return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender} +} + +// Creates a new QML Block from a chain block +func NewBlockFromBlock(block *ethchain.Block) *Block { + info := block.BlockInfo() + hash := hex.EncodeToString(block.Hash()) + + return &Block{Number: int(info.Number), Hash: hash} +} + +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 + lib *EthLib + + txDb *ethdb.LDBDatabase + + addr []byte +} + +// Create GUI, but doesn't start it +func New(ethereum *eth.Ethereum) *Gui { + lib := &EthLib{stateManager: ethereum.StateManager(), blockChain: ethereum.BlockChain(), txPool: ethereum.TxPool()} + db, err := ethdb.NewLDBDatabase("tx_database") + if err != nil { + panic(err) + } + + key := ethutil.Config.Db.GetKeys()[0] + addr := key.Address() + + ethereum.StateManager().WatchAddr(addr) + + return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} +} + +func (ui *Gui) Start() { + defer ui.txDb.Close() + + // Register ethereum functions + qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ + Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, + }, { + Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, + }}) + + ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.1")) + ethutil.Config.Log.Infoln("[GUI] Starting GUI") + // Create a new QML engine + ui.engine = qml.NewEngine() + context := ui.engine.Context() + + // Expose the eth library and the ui library to QML + context.SetVar("eth", ui.lib) + context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) + + // Load the main QML interface + component, err := ui.engine.LoadFile(AssetPath("qml/wallet.qml")) + if err != nil { + panic(err) + } + ui.engine.LoadFile(AssetPath("qml/transactions.qml")) + + ui.win = component.CreateWindow(nil) + + // Register the ui as a block processor + //ui.eth.BlockManager.SecondaryBlockProcessor = ui + //ui.eth.TxPool.SecondaryProcessor = ui + + // Add the ui as a log system so we can log directly to the UGI + ethutil.Config.Log.AddLogSystem(ui) + + // Loads previous blocks + go ui.setInitialBlockChain() + go ui.readPreviousTransactions() + go ui.update() + + ui.win.Show() + ui.win.Wait() + + ui.eth.Stop() +} + +func (ui *Gui) setInitialBlockChain() { + // Load previous 10 blocks + chain := ui.eth.BlockChain().GetChain(ui.eth.BlockChain().CurrentBlock.Hash(), 10) + for _, block := range chain { + ui.ProcessBlock(block) + } + +} + +func (ui *Gui) readPreviousTransactions() { + it := ui.txDb.Db().NewIterator(nil, nil) + for it.Next() { + tx := ethchain.NewTransactionFromBytes(it.Value()) + + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + } + it.Release() +} + +func (ui *Gui) ProcessBlock(block *ethchain.Block) { + ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) +} + +// Simple go routine function that updates the list of peers in the GUI +func (ui *Gui) update() { + txChan := make(chan ethchain.TxMsg, 1) + ui.eth.TxPool().Subscribe(txChan) + + account := ui.eth.StateManager().GetAddrState(ui.addr).Account + unconfirmedFunds := new(big.Int) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) + for { + select { + case txMsg := <-txChan: + tx := txMsg.Tx + + if txMsg.Type == ethchain.TxPre { + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + ui.eth.StateManager().GetAddrState(ui.addr).Nonce += 1 + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + unconfirmedFunds.Add(unconfirmedFunds, tx.Value) + } + + 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(account.Amount), pos, val) + + ui.win.Root().Call("setWalletValue", str) + } else { + amount := account.Amount + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + amount.Sub(account.Amount, tx.Value) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + amount.Add(account.Amount, tx.Value) + } + + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) + } + } + + /* + accountAmount := ui.eth.BlockManager.GetAddrState(ui.addr).Account.Amount + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", accountAmount)) + + ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) + + time.Sleep(1 * time.Second) + */ + + } +} + +// Logging functions that log directly to the GUI interface +func (ui *Gui) Println(v ...interface{}) { + str := strings.TrimRight(fmt.Sprintln(v...), "\n") + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } +} + +func (ui *Gui) Printf(format string, v ...interface{}) { + str := strings.TrimRight(fmt.Sprintf(format, v...), "\n") + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } +} diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go new file mode 100644 index 000000000..05fffd579 --- /dev/null +++ b/ethereal/ui/library.go @@ -0,0 +1,60 @@ +package ethui + +import ( + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "strings" +) + +type EthLib struct { + stateManager *ethchain.StateManager + blockChain *ethchain.BlockChain + txPool *ethchain.TxPool +} + +func (lib *EthLib) CreateTx(receiver, a, data string) string { + var hash []byte + if len(receiver) == 0 { + hash = ethchain.ContractAddr + } else { + var err error + hash, err = hex.DecodeString(receiver) + if err != nil { + return err.Error() + } + } + + k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyPair := ethutil.NewKeyFromBytes(k) + + amount := ethutil.Big(a) + code := ethchain.Compile(strings.Split(data, "\n")) + tx := ethchain.NewTransaction(hash, amount, code) + tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce + + tx.Sign(keyPair.PrivateKey) + + lib.txPool.QueueTransaction(tx) + + if len(receiver) == 0 { + ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } else { + ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) + } + + return ethutil.Hex(tx.Hash()) +} + +func (lib *EthLib) GetBlock(hexHash string) *Block { + hash, err := hex.DecodeString(hexHash) + if err != nil { + return nil + } + + block := lib.blockChain.GetBlock(hash) + fmt.Println(block) + + return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} +} diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go new file mode 100644 index 000000000..edff02286 --- /dev/null +++ b/ethereal/ui/ui_lib.go @@ -0,0 +1,75 @@ +package ethui + +import ( + "bitbucket.org/kardianos/osext" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethutil" + "github.com/niemeyer/qml" + "os" + "path" + "path/filepath" + "runtime" +) + +// UI Library that has some basic functionality exposed +type UiLib struct { + engine *qml.Engine + eth *eth.Ethereum + connected bool +} + +// Opens a QML file (external application) +func (ui *UiLib) Open(path string) { + component, err := ui.engine.LoadFile(path[7:]) + if err != nil { + ethutil.Config.Log.Debugln(err) + } + win := component.CreateWindow(nil) + + go func() { + win.Show() + win.Wait() + }() +} + +func (ui *UiLib) Connect(button qml.Object) { + if !ui.connected { + ui.eth.Start() + 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 AssetPath(p) +} + +func AssetPath(p string) string { + var base string + // If the current working directory is the go-ethereum dir + // assume a debug build and use the source directory as + // asset directory. + pwd, _ := os.Getwd() + if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "ethereal") { + base = path.Join(pwd, "assets") + } else { + switch runtime.GOOS { + case "darwin": + // Get Binary Directory + exedir, _ := osext.ExecutableFolder() + base = filepath.Join(exedir, "../Resources") + case "linux": + base = "/usr/share/ethereal" + case "window": + fallthrough + default: + base = "." + } + } + + return path.Join(base, p) +} |