diff options
author | obscuren <geffobscura@gmail.com> | 2014-09-23 01:33:24 +0800 |
---|---|---|
committer | obscuren <geffobscura@gmail.com> | 2014-09-23 01:33:24 +0800 |
commit | 154ca03228a9d97a4b2319f525814a16f102430f (patch) | |
tree | a6e71cdafb18f011c2472d4792075173c132bee6 | |
parent | 7855a233a7ed4968d93fc76a74501c931574f6eb (diff) | |
parent | c7d666ad61a76e79867c9f7ae783a2cc44485dd0 (diff) | |
download | go-tangerine-154ca03228a9d97a4b2319f525814a16f102430f.tar go-tangerine-154ca03228a9d97a4b2319f525814a16f102430f.tar.gz go-tangerine-154ca03228a9d97a4b2319f525814a16f102430f.tar.bz2 go-tangerine-154ca03228a9d97a4b2319f525814a16f102430f.tar.lz go-tangerine-154ca03228a9d97a4b2319f525814a16f102430f.tar.xz go-tangerine-154ca03228a9d97a4b2319f525814a16f102430f.tar.zst go-tangerine-154ca03228a9d97a4b2319f525814a16f102430f.zip |
Merge branch 'release/0.6.5'v0.6.5
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | ethereal/assets/debugger/debugger.qml | 377 | ||||
-rw-r--r-- | ethereal/assets/ext/filter.js | 31 | ||||
-rw-r--r-- | ethereal/assets/ext/pre.js | 37 | ||||
-rw-r--r-- | ethereal/assets/qml/wallet.qml | 753 | ||||
-rw-r--r-- | ethereal/assets/qml/webapp.qml | 347 | ||||
-rw-r--r-- | ethereum/flags.go | 1 | ||||
-rw-r--r-- | ethereum/main.go | 10 | ||||
-rw-r--r-- | javascript/javascript_runtime.go | 2 | ||||
-rw-r--r-- | javascript/types.go | 4 | ||||
-rw-r--r-- | mist/assets/back.png (renamed from ethereal/assets/back.png) | bin | 1004 -> 1004 bytes | |||
-rw-r--r-- | mist/assets/browser.png | bin | 0 -> 12903 bytes | |||
-rw-r--r-- | mist/assets/bug.png (renamed from ethereal/assets/bug.png) | bin | 1671 -> 1671 bytes | |||
-rw-r--r-- | mist/assets/close.png (renamed from ethereal/assets/close.png) | bin | 905 -> 905 bytes | |||
-rw-r--r-- | mist/assets/debugger/debugger.qml | 435 | ||||
-rw-r--r-- | mist/assets/ext/big.js (renamed from ethereal/assets/ext/big.js) | 0 | ||||
-rw-r--r-- | mist/assets/ext/ethereum.js (renamed from ethereal/assets/ext/ethereum.js) | 0 | ||||
-rw-r--r-- | mist/assets/ext/filter.js | 49 | ||||
-rw-r--r-- | mist/assets/ext/home.html (renamed from ethereal/assets/ext/home.html) | 0 | ||||
-rw-r--r-- | mist/assets/ext/html_messaging.js | 481 | ||||
-rw-r--r-- | mist/assets/ext/http.js | 13 | ||||
-rw-r--r-- | mist/assets/ext/pre.js | 3 | ||||
-rw-r--r-- | mist/assets/ext/q.js | 1909 | ||||
-rw-r--r-- | mist/assets/ext/qml_messaging.js | 13 | ||||
-rw-r--r-- | mist/assets/ext/string.js (renamed from ethereal/assets/ext/string.js) | 0 | ||||
-rw-r--r-- | mist/assets/ext/test.html (renamed from ethereal/assets/ext/test.html) | 0 | ||||
-rw-r--r-- | mist/assets/facet.png (renamed from ethereal/assets/facet.png) | bin | 27302 -> 27302 bytes | |||
-rw-r--r-- | mist/assets/heart.png (renamed from ethereal/assets/heart.png) | bin | 4277 -> 4277 bytes | |||
-rw-r--r-- | mist/assets/icecream.png | bin | 0 -> 4643 bytes | |||
-rw-r--r-- | mist/assets/muted/codemirror.css (renamed from ethereal/assets/muted/codemirror.css) | 0 | ||||
-rw-r--r-- | mist/assets/muted/debugger.html (renamed from ethereal/assets/muted/debugger.html) | 0 | ||||
-rw-r--r-- | mist/assets/muted/eclipse.css (renamed from ethereal/assets/muted/eclipse.css) | 0 | ||||
-rw-r--r-- | mist/assets/muted/index.html (renamed from ethereal/assets/muted/index.html) | 0 | ||||
-rw-r--r-- | mist/assets/muted/lib/codemirror.js (renamed from ethereal/assets/muted/lib/codemirror.js) | 0 | ||||
-rw-r--r-- | mist/assets/muted/lib/go.js (renamed from ethereal/assets/muted/lib/go.js) | 0 | ||||
-rw-r--r-- | mist/assets/muted/lib/matchbrackets.js (renamed from ethereal/assets/muted/lib/matchbrackets.js) | 0 | ||||
-rw-r--r-- | mist/assets/muted/muted.js (renamed from ethereal/assets/muted/muted.js) | 0 | ||||
-rw-r--r-- | mist/assets/net.png (renamed from ethereal/assets/net.png) | bin | 4669 -> 4669 bytes | |||
-rw-r--r-- | mist/assets/network.png (renamed from ethereal/assets/network.png) | bin | 2900 -> 2900 bytes | |||
-rw-r--r-- | mist/assets/new.png (renamed from ethereal/assets/new.png) | bin | 4776 -> 4776 bytes | |||
-rw-r--r-- | mist/assets/pick.png (renamed from ethereal/assets/pick.png) | bin | 932 -> 932 bytes | |||
-rw-r--r-- | mist/assets/qml/QmlApp.qml (renamed from ethereal/assets/qml/QmlApp.qml) | 0 | ||||
-rw-r--r-- | mist/assets/qml/first_run.qml (renamed from ethereal/assets/qml/first_run.qml) | 0 | ||||
-rw-r--r-- | mist/assets/qml/muted.qml (renamed from ethereal/assets/qml/muted.qml) | 0 | ||||
-rw-r--r-- | mist/assets/qml/test_app.qml (renamed from ethereal/assets/qml/test_app.qml) | 0 | ||||
-rw-r--r-- | mist/assets/qml/transactions.qml (renamed from ethereal/assets/qml/transactions.qml) | 0 | ||||
-rw-r--r-- | mist/assets/qml/views/chain.qml (renamed from ethereal/assets/qml/views/chain.qml) | 20 | ||||
-rw-r--r-- | mist/assets/qml/views/history.qml (renamed from ethereal/assets/qml/views/history.qml) | 0 | ||||
-rw-r--r-- | mist/assets/qml/views/info.qml (renamed from ethereal/assets/qml/views/info.qml) | 129 | ||||
-rw-r--r-- | mist/assets/qml/views/javascript.qml (renamed from ethereal/assets/qml/views/javascript.qml) | 0 | ||||
-rw-r--r-- | mist/assets/qml/views/jeffcoin/jeff.png | bin | 0 -> 84076 bytes | |||
-rw-r--r-- | mist/assets/qml/views/jeffcoin/jeffcoin.qml | 184 | ||||
-rw-r--r-- | mist/assets/qml/views/pending_tx.qml (renamed from ethereal/assets/qml/views/pending_tx.qml) | 0 | ||||
-rw-r--r-- | mist/assets/qml/views/transaction.qml (renamed from ethereal/assets/qml/views/transaction.qml) | 2 | ||||
-rw-r--r-- | mist/assets/qml/views/wallet.qml (renamed from ethereal/assets/qml/views/wallet.qml) | 14 | ||||
-rw-r--r-- | mist/assets/qml/wallet.qml | 894 | ||||
-rw-r--r-- | mist/assets/qml/webapp.qml | 409 | ||||
-rw-r--r-- | mist/assets/tx.png (renamed from ethereal/assets/tx.png) | bin | 4070 -> 4070 bytes | |||
-rw-r--r-- | mist/assets/util/test.html (renamed from ethereal/assets/util/test.html) | 0 | ||||
-rw-r--r-- | mist/assets/wallet.png (renamed from ethereal/assets/wallet.png) | bin | 1114 -> 1114 bytes | |||
-rw-r--r-- | mist/bindings.go | 148 | ||||
-rw-r--r-- | mist/debugger.go (renamed from ethereal/debugger.go) | 34 | ||||
-rw-r--r-- | mist/errors.go | 36 | ||||
-rw-r--r-- | mist/ext_app.go (renamed from ethereal/ext_app.go) | 0 | ||||
-rw-r--r-- | mist/flags.go (renamed from ethereal/flags.go) | 13 | ||||
-rw-r--r-- | mist/gui.go (renamed from ethereal/gui.go) | 224 | ||||
-rw-r--r-- | mist/html_container.go (renamed from ethereal/html_container.go) | 0 | ||||
-rw-r--r-- | mist/main.go (renamed from ethereal/main.go) | 14 | ||||
-rw-r--r-- | mist/qml_container.go (renamed from ethereal/qml_container.go) | 0 | ||||
-rw-r--r-- | mist/ui_lib.go (renamed from ethereal/ui_lib.go) | 110 | ||||
-rw-r--r-- | utils/cmd.go | 23 |
71 files changed, 4910 insertions, 1811 deletions
@@ -7,7 +7,7 @@ Status](http://cpt-obvious.ethercasts.com:8010/buildstatusimage?builder=go-ether Ethereum Go Client © 2014 Jeffrey Wilcke. -Current state: Proof of Concept 0.6.4. +Current state: Proof of Concept 0.6.5. For the development package please see the [eth-go package](https://github.com/ethereum/eth-go). diff --git a/ethereal/assets/debugger/debugger.qml b/ethereal/assets/debugger/debugger.qml deleted file mode 100644 index 34fe01253..000000000 --- a/ethereal/assets/debugger/debugger.qml +++ /dev/null @@ -1,377 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0; -import QtQuick.Layouts 1.0; -import QtQuick.Dialogs 1.0; -import QtQuick.Window 2.1; -import QtQuick.Controls.Styles 1.1 -import Ethereum 1.0 - -ApplicationWindow { - id: win - visible: false - title: "IceCREAM" - minimumWidth: 1280 - minimumHeight: 700 - width: 1290 - height: 750 - - property alias codeText: codeEditor.text - property alias dataText: rawDataField.text - - onClosing: { - //compileTimer.stop() - } - - MenuBar { - Menu { - title: "Debugger" - MenuItem { - text: "Run" - shortcut: "Ctrl+r" - onTriggered: debugCurrent() - } - - MenuItem { - text: "Next" - shortcut: "Ctrl+n" - onTriggered: dbg.next() - } - - MenuItem { - text: "Continue" - shortcut: "Ctrl+g" - onTriggered: dbg.continue() - } - MenuItem { - text: "Command" - shortcut: "Ctrl+l" - onTriggered: { - dbgCommand.focus = true - } - } - MenuItem { - text: "Focus code" - shortcut: "Ctrl+1" - onTriggered: { - codeEditor.focus = true - } - } - MenuItem { - text: "Focus data" - shortcut: "Ctrl+2" - onTriggered: { - rawDataField.focus = true - } - } - - /* - MenuItem { - text: "Close window" - shortcut: "Ctrl+w" - onTriggered: { - win.close() - } - } - */ - } - } - - - SplitView { - anchors.fill: parent - property var asmModel: ListModel { - id: asmModel - } - - TableView { - id: asmTableView - width: 200 - TableViewColumn{ role: "value" ; title: "" ; width: asmTableView.width - 2 } - model: asmModel - } - - Rectangle { - color: "#00000000" - anchors.left: asmTableView.right - anchors.right: parent.right - SplitView { - orientation: Qt.Vertical - anchors.fill: parent - - Rectangle { - color: "#00000000" - height: 330 - anchors.left: parent.left - anchors.right: parent.right - - TextArea { - id: codeEditor - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: settings.left - focus: true - - /* - Timer { - id: compileTimer - interval: 500 ; running: true ; repeat: true - onTriggered: { - dbg.autoComp(codeEditor.text) - } - } - */ - } - - Column { - id: settings - spacing: 5 - width: 300 - height: parent.height - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - Label { - text: "Arbitrary data" - } - TextArea { - id: rawDataField - anchors.left: parent.left - anchors.right: parent.right - height: 150 - } - - Label { - text: "Amount" - } - TextField { - id: txValue - width: 200 - placeholderText: "Amount" - validator: RegExpValidator { regExp: /\d*/ } - } - Label { - text: "Amount of gas" - } - TextField { - id: txGas - width: 200 - validator: RegExpValidator { regExp: /\d*/ } - text: "10000" - placeholderText: "Gas" - } - Label { - text: "Gas price" - } - TextField { - id: txGasPrice - width: 200 - placeholderText: "Gas price" - text: "1000000000000" - validator: RegExpValidator { regExp: /\d*/ } - } - } - } - - SplitView { - orientation: Qt.Vertical - id: inspectorPane - height: 500 - - SplitView { - orientation: Qt.Horizontal - height: 150 - - TableView { - id: stackTableView - property var stackModel: ListModel { - id: stackModel - } - height: parent.height - width: 300 - TableViewColumn{ role: "value" ; title: "Temp" ; width: 200 } - model: stackModel - } - - TableView { - id: memoryTableView - property var memModel: ListModel { - id: memModel - } - height: parent.height - width: parent.width - stackTableView.width - TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50} - TableViewColumn{ role: "value" ; title: "Memory" ; width: 750} - model: memModel - } - } - - Rectangle { - height: 100 - width: parent.width - TableView { - id: storageTableView - property var memModel: ListModel { - id: storageModel - } - height: parent.height - width: parent.width - TableViewColumn{ id: key ; role: "key" ; title: "#" ; width: storageTableView.width / 2} - TableViewColumn{ role: "value" ; title: "Storage" ; width: storageTableView.width / 2} - model: storageModel - } - } - - Rectangle { - height: 200 - width: parent.width - TableView { - id: logTableView - property var logModel: ListModel { - id: logModel - } - height: parent.height - width: parent.width - TableViewColumn{ id: message ; role: "message" ; title: "log" ; width: logTableView.width - 2 } - model: logModel - } - } - } - } - } - } - - function exec() { - dbg.execCommand(dbgCommand.text); - dbgCommand.text = ""; - } - statusBar: StatusBar { - height: 30 - - - TextField { - id: dbgCommand - y: 1 - x: asmTableView.width - width: 500 - placeholderText: "Debugger (type 'help')" - Keys.onReturnPressed: { - exec() - } - } - } - - toolBar: ToolBar { - height: 30 - RowLayout { - spacing: 5 - - Button { - property var enabled: true - id: debugStart - onClicked: { - debugCurrent() - } - text: "Debug" - } - - Button { - property var enabled: true - id: debugNextButton - onClicked: { - dbg.next() - } - text: "Next" - } - - Button { - id: debugContinueButton - onClicked: { - dbg.continue() - } - text: "Continue" - } - } - - - ComboBox { - id: snippets - anchors.right: parent.right - model: ListModel { - ListElement { text: "Snippets" ; value: "" } - ListElement { text: "Call Contract" ; value: "var[2] in;\nvar ret;\n\nin[0] = \"arg1\"\nin[1] = 0xdeadbeef\n\nvar success = call(0x0c542ddea93dae0c2fcb2cf175f03ad80d6be9a0, 0, 7000, in, ret)\n\nreturn ret" } - } - onCurrentIndexChanged: { - if(currentIndex != 0) { - var code = snippets.model.get(currentIndex).value; - codeEditor.insert(codeEditor.cursorPosition, code) - } - } - } - - } - - function debugCurrent() { - dbg.debug(txValue.text, txGas.text, txGasPrice.text, codeEditor.text, rawDataField.text) - } - - function setAsm(asm) { - asmModel.append({asm: asm}) - } - - function clearAsm() { - asmModel.clear() - } - - function setInstruction(num) { - asmTableView.selection.clear() - asmTableView.selection.select(num) - } - - function setMem(mem) { - memModel.append({num: mem.num, value: mem.value}) - } - function clearMem(){ - memModel.clear() - } - - function setStack(stack) { - stackModel.append({value: stack}) - } - function addDebugMessage(message){ - debuggerLog.append({value: message}) - } - - function clearStack() { - stackModel.clear() - } - - function clearStorage() { - storageModel.clear() - } - - function setStorage(storage) { - storageModel.append({key: storage.key, value: storage.value}) - } - - function setLog(msg) { - // Remove first item once we've reached max log items - if(logModel.count > 250) { - logModel.remove(0) - } - - if(msg.len != 0) { - if(logTableView.flickableItem.atYEnd) { - logModel.append({message: msg}) - logTableView.positionViewAtRow(logTableView.rowCount - 1, ListView.Contain) - } else { - logModel.append({message: msg}) - } - } - } - - function clearLog() { - logModel.clear() - } -} diff --git a/ethereal/assets/ext/filter.js b/ethereal/assets/ext/filter.js deleted file mode 100644 index 5c1c03aad..000000000 --- a/ethereal/assets/ext/filter.js +++ /dev/null @@ -1,31 +0,0 @@ -var Filter = function(options) { - this.callbacks = {}; - this.seed = Math.floor(Math.random() * 1000000); - this.options = options; - - if(options == "chain") { - eth.registerFilterString(options, this.seed); - } else if(typeof options === "object") { - eth.registerFilter(options, this.seed); - } -}; - -Filter.prototype.changed = function(callback) { - var cbseed = Math.floor(Math.random() * 1000000); - eth.registerFilterCallback(this.seed, cbseed); - - var self = this; - message.connect(function(messages, seed, callbackSeed) { - if(seed == self.seed && callbackSeed == cbseed) { - callback.call(self, messages); - } - }); -}; - -Filter.prototype.uninstall = function() { - eth.uninstallFilter(this.seed) -} - -Filter.prototype.messages = function() { - return JSON.parse(eth.messages(this.options)) -} diff --git a/ethereal/assets/ext/pre.js b/ethereal/assets/ext/pre.js deleted file mode 100644 index 3e8a534e9..000000000 --- a/ethereal/assets/ext/pre.js +++ /dev/null @@ -1,37 +0,0 @@ -// Helper function for generating pseudo callbacks and sending data to the QML part of the application -function postData(data, cb) { - data._seed = Math.floor(Math.random() * 1000000) - if(cb) { - eth._callbacks[data._seed] = cb; - } - - if(data.args === undefined) { - data.args = []; - } - - navigator.qt.postMessage(JSON.stringify(data)); -} - -navigator.qt.onmessage = function(ev) { - var data = JSON.parse(ev.data) - - if(data._event !== undefined) { - eth.trigger(data._event, data.data); - } else { - if(data._seed) { - var cb = eth._callbacks[data._seed]; - if(cb) { - // Figure out whether the returned data was an array - // array means multiple return arguments (multiple params) - if(data.data instanceof Array) { - cb.apply(this, data.data) - } else { - cb.call(this, data.data) - } - - // Remove the "trigger" callback - delete eth._callbacks[ev._seed]; - } - } - } -} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml deleted file mode 100644 index 90cc42a1f..000000000 --- a/ethereal/assets/qml/wallet.qml +++ /dev/null @@ -1,753 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0; -import QtQuick.Layouts 1.0; -import QtQuick.Dialogs 1.0; -import QtQuick.Window 2.1; -import QtQuick.Controls.Styles 1.1 -import Ethereum 1.0 - -import "../ext/filter.js" as Eth - -ApplicationWindow { - id: root - - property alias miningButtonText: miningButton.text - - - width: 900 - height: 600 - minimumHeight: 300 - - title: "Ether browser" - - // This signal is used by the filter API. The filter API connects using this signal handler from - // the different QML files and plugins. - signal message(var callback, int seed, int seedCallback); - function invokeFilterCallback(data, receiverSeed, callbackSeed) { - var messages = JSON.parse(data) - // Signal handler - message(messages, receiverSeed, callbackSeed); - } - - TextField { - id: copyElementHax - visible: false - } - - function copyToClipboard(text) { - copyElementHax.text = text - copyElementHax.selectAll() - copyElementHax.copy() - } - - // Takes care of loading all default plugins - Component.onCompleted: { - var walletView = addPlugin("./views/wallet.qml", {noAdd: true, section: "ethereum", active: true}) - var historyView = addPlugin("./views/history.qml", {noAdd: true, section: "legacy"}) - var newTxView = addPlugin("./views/transaction.qml", {noAdd: true, section: "legacy"}) - var chainView = addPlugin("./views/chain.qml", {noAdd: true, section: "legacy"}) - var infoView = addPlugin("./views/info.qml", {noAdd: true, section: "legacy"}) - var pendingTxView = addPlugin("./views/pending_tx.qml", {noAdd: true, section: "legacy"}) - var pendingTxView = addPlugin("./views/javascript.qml", {noAdd: true, section: "legacy"}) - - // Call the ready handler - gui.done() - - } - - function addPlugin(path, options) { - var component = Qt.createComponent(path); - if(component.status != Component.Ready) { - if(component.status == Component.Error) { - console.debug("Error:"+ component.errorString()); - } - - return - } - - var views = mainSplit.addComponent(component, options) - views.menuItem.path = path - - mainSplit.views.push(views); - - if(!options.noAdd) { - gui.addPlugin(path) - } - - return views.view - } - - MenuBar { - Menu { - title: "File" - MenuItem { - text: "Import App" - shortcut: "Ctrl+o" - onTriggered: { - generalFileDialog.show(true, importApp) - } - } - - MenuItem { - text: "Browser" - onTriggered: eth.openBrowser() - } - - MenuItem { - text: "Add plugin" - onTriggered: { - generalFileDialog.show(true, function(path) { - addPlugin(path, {canClose: true, section: "apps"}) - }) - } - } - - MenuSeparator {} - - MenuItem { - text: "Import key" - shortcut: "Ctrl+i" - onTriggered: { - generalFileDialog.show(true, function(path) { - gui.importKey(path) - }) - } - } - - MenuItem { - text: "Export keys" - shortcut: "Ctrl+e" - onTriggered: { - generalFileDialog.show(false, function(path) { - }) - } - } - } - - Menu { - title: "Developer" - MenuItem { - text: "Debugger" - shortcut: "Ctrl+d" - onTriggered: eth.startDebugger() - } - - MenuItem { - text: "Import Tx" - onTriggered: { - txImportDialog.visible = true - } - } - - MenuItem { - text: "Run JS file" - onTriggered: { - generalFileDialog.show(true, function(path) { - eth.evalJavascriptFile(path) - }) - } - } - - MenuItem { - text: "Dump state" - onTriggered: { - generalFileDialog.show(false, function(path) { - // Empty hash for latest - gui.dumpState("", path) - }) - } - } - - MenuSeparator {} - - MenuItem { - id: miningSpeed - text: "Mining: Turbo" - onTriggered: { - gui.toggleTurboMining() - if(text == "Mining: Turbo") { - text = "Mining: Normal"; - } else { - text = "Mining: Turbo"; - } - } - } - } - - Menu { - title: "Network" - MenuItem { - text: "Add Peer" - shortcut: "Ctrl+p" - onTriggered: { - addPeerWin.visible = true - } - } - MenuItem { - text: "Show Peers" - shortcut: "Ctrl+e" - onTriggered: { - peerWindow.visible = true - } - } - } - - Menu { - title: "Help" - MenuItem { - text: "About" - onTriggered: { - aboutWin.visible = true - } - } - } - - } - - statusBar: StatusBar { - height: 32 - RowLayout { - Button { - id: miningButton - text: "Start Mining" - onClicked: { - gui.toggleMining() - } - } - - Button { - id: importAppButton - text: "Browser" - onClicked: { - eth.openBrowser() - } - } - - RowLayout { - Label { - id: walletValueLabel - - font.pixelSize: 10 - styleColor: "#797979" - } - } - } - - Label { - y: 6 - objectName: "miningLabel" - visible: true - font.pixelSize: 10 - anchors.right: lastBlockLabel.left - anchors.rightMargin: 5 - } - - Label { - y: 6 - id: lastBlockLabel - objectName: "lastBlockLabel" - visible: true - text: "" - font.pixelSize: 10 - anchors.right: peerGroup.left - anchors.rightMargin: 5 - } - - ProgressBar { - id: syncProgressIndicator - visible: false - objectName: "syncProgressIndicator" - y: 3 - width: 140 - indeterminate: true - anchors.right: peerGroup.left - anchors.rightMargin: 5 - } - - RowLayout { - id: peerGroup - y: 7 - anchors.right: parent.right - MouseArea { - onDoubleClicked: peerWindow.visible = true - anchors.fill: parent - } - - Label { - id: peerLabel - font.pixelSize: 8 - text: "0 / 0" - } - Image { - id: peerImage - width: 10; height: 10 - source: "../network.png" - } - } - } - - - property var blockModel: ListModel { - id: blockModel - } - - SplitView { - property var views: []; - - id: mainSplit - anchors.fill: parent - resizing: false - - function setView(view, menu) { - for(var i = 0; i < views.length; i++) { - views[i].view.visible = false - - views[i].menuItem.border.color = "#00000000" - views[i].menuItem.color = "#00000000" - } - view.visible = true - - menu.border.color = "#CCCCCC" - menu.color = "#FFFFFFFF" - } - - function addComponent(component, options) { - var view = mainView.createView(component, options) - view.visible = false - view.anchors.fill = mainView - - if( !view.hasOwnProperty("iconSource") ) { - console.log("Could not load plugin. Property 'iconSourc' not found on view."); - return; - } - - var menuItem = menu.createMenuItem(view.iconSource, view, options); - if( view.hasOwnProperty("menuItem") ) { - view.menuItem = menuItem; - } - - if( view.hasOwnProperty("onReady") ) { - view.onReady.call(view) - } - - if( options.active ) { - setView(view, menuItem) - } - - - return {view: view, menuItem: menuItem} - } - - /********************* - * Main menu. - ********************/ - Rectangle { - id: menu - Layout.minimumWidth: 180 - Layout.maximumWidth: 180 - anchors.top: parent.top - color: "#ececec" - - Component { - id: menuItemTemplate - Rectangle { - id: menuItem - property var view; - property var path; - - property alias title: label.text - property alias icon: icon.source - property alias secondaryTitle: secondary.text - - width: 180 - height: 28 - border.color: "#00000000" - border.width: 1 - radius: 5 - color: "#00000000" - - anchors { - left: parent.left - leftMargin: 4 - } - - MouseArea { - anchors.fill: parent - onClicked: { - mainSplit.setView(view, menuItem) - } - } - - Image { - id: icon - height: 20 - width: 20 - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - leftMargin: 3 - } - MouseArea { - anchors.fill: parent - onClicked: { - menuItem.closeApp() - } - } - } - - Text { - id: label - anchors { - left: icon.right - verticalCenter: parent.verticalCenter - leftMargin: 3 - } - - color: "#0D0A01" - font.pixelSize: 12 - } - - Text { - id: secondary - anchors { - right: parent.right - rightMargin: 8 - verticalCenter: parent.verticalCenter - } - color: "#AEADBE" - font.pixelSize: 12 - } - - - function closeApp() { - if(this.view.hasOwnProperty("onDestroy")) { - this.view.onDestroy.call(this.view) - } - - this.view.destroy() - this.destroy() - gui.removePlugin(this.path) - } - } - } - - function createMenuItem(icon, view, options) { - if(options === undefined) { - options = {}; - } - - var section; - switch(options.section) { - case "ethereum": - section = menuDefault; - break; - case "legacy": - section = menuLegacy; - break; - default: - section = menuApps; - break; - } - - var comp = menuItemTemplate.createObject(section) - - comp.view = view - comp.title = view.title - comp.icon = view.iconSource - /* - if(view.secondary !== undefined) { - comp.secondary = view.secondary - } - */ - - return comp - - /* - if(options.canClose) { - //comp.closeButton.visible = options.canClose - } - */ - } - - ColumnLayout { - id: menuColumn - y: 10 - width: parent.width - anchors.left: parent.left - anchors.right: parent.right - spacing: 3 - - Text { - text: "ETHEREUM" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuDefault - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - - - Text { - text: "APPS" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuApps - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - - Text { - text: "DEBUG" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuLegacy - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - } - } - - /********************* - * Main view - ********************/ - Rectangle { - id: mainView - color: "#00000000" - - anchors.right: parent.right - anchors.left: menu.right - anchors.bottom: parent.bottom - anchors.top: parent.top - - function createView(component) { - var view = component.createObject(mainView) - - return view; - } - } - - - } - - - /****************** - * Dialogs - *****************/ - FileDialog { - id: generalFileDialog - property var callback; - onAccepted: { - var path = this.fileUrl.toString(); - callback.call(this, path); - } - - function show(selectExisting, callback) { - generalFileDialog.callback = callback; - generalFileDialog.selectExisting = selectExisting; - - this.open(); - } - } - - - /****************** - * Wallet functions - *****************/ - function importApp(path) { - var ext = path.split('.').pop() - if(ext == "html" || ext == "htm") { - eth.openHtml(path) - }else if(ext == "qml"){ - addPlugin(path, {canClose: true, section: "apps"}) - } - } - - - function setWalletValue(value) { - walletValueLabel.text = value - } - - function loadPlugin(name) { - console.log("Loading plugin" + name) - var view = mainView.addPlugin(name) - } - - function setPeers(text) { - peerLabel.text = text - } - - function addPeer(peer) { - // We could just append the whole peer object but it cries if you try to alter them - peerModel.append({ip: peer.ip, port: peer.port, lastResponse:timeAgo(peer.lastSend), latency: peer.latency, version: peer.version}) - } - - function resetPeers(){ - peerModel.clear() - } - - function timeAgo(unixTs){ - var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000 - return (lapsed + " seconds ago") - } - - function convertToPretty(unixTs){ - var a = new Date(unixTs*1000); - var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; - var year = a.getFullYear(); - var month = months[a.getMonth()]; - var date = a.getDate(); - var hour = a.getHours(); - var min = a.getMinutes(); - var sec = a.getSeconds(); - var time = date+' '+month+' '+year+' '+hour+':'+min+':'+sec ; - return time; - } - - /********************** - * Windows - *********************/ - Window { - id: peerWindow - //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint - height: 200 - width: 700 - Rectangle { - anchors.fill: parent - property var peerModel: ListModel { - id: peerModel - } - TableView { - anchors.fill: parent - id: peerTable - model: peerModel - TableViewColumn{width: 100; role: "ip" ; title: "IP" } - TableViewColumn{width: 60; role: "port" ; title: "Port" } - TableViewColumn{width: 140; role: "lastResponse"; title: "Last event" } - TableViewColumn{width: 100; role: "latency"; title: "Latency" } - TableViewColumn{width: 260; role: "version" ; title: "Version" } - } - } - } - - Window { - id: aboutWin - visible: false - title: "About" - minimumWidth: 350 - maximumWidth: 350 - maximumHeight: 200 - minimumHeight: 200 - - Image { - id: aboutIcon - height: 150 - width: 150 - fillMode: Image.PreserveAspectFit - smooth: true - source: "../facet.png" - x: 10 - y: 10 - } - - Text { - anchors.left: aboutIcon.right - anchors.leftMargin: 10 - font.pointSize: 12 - text: "<h2>Ethereal - Aitne</h2><br><h3>Development</h3>Jeffrey Wilcke<br>Maran Hidskes<br>Viktor Trón<br>" - } - } - - Window { - id: txImportDialog - minimumWidth: 270 - maximumWidth: 270 - maximumHeight: 50 - minimumHeight: 50 - TextField { - id: txImportField - width: 170 - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - onAccepted: { - } - } - Button { - anchors.left: txImportField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Import" - onClicked: { - eth.importTx(txImportField.text) - txImportField.visible = false - } - } - Component.onCompleted: { - addrField.focus = true - } - } - - Window { - id: addPeerWin - visible: false - minimumWidth: 230 - maximumWidth: 230 - maximumHeight: 50 - minimumHeight: 50 - - TextField { - id: addrField - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - placeholderText: "address:port" - onAccepted: { - eth.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Button { - anchors.left: addrField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Add" - onClicked: { - eth.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Component.onCompleted: { - addrField.focus = true - } - } - } diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml deleted file mode 100644 index ca6860036..000000000 --- a/ethereal/assets/qml/webapp.qml +++ /dev/null @@ -1,347 +0,0 @@ -import QtQuick 2.0 -import QtWebKit 3.0 -import QtWebKit.experimental 1.0 -import QtQuick.Controls 1.0; -import QtQuick.Controls.Styles 1.0 -import QtQuick.Layouts 1.0; -import QtQuick.Window 2.1; -import Ethereum 1.0 - -ApplicationWindow { - id: window - title: "Ethereum" - width: 1000 - height: 800 - minimumHeight: 300 - - property alias url: webview.url - property alias webView: webview - - Item { - objectName: "root" - id: root - anchors.fill: parent - state: "inspectorShown" - - RowLayout { - id: navBar - height: 40 - anchors { - left: parent.left - right: parent.right - leftMargin: 7 - } - - Button { - id: back - onClicked: { - webview.goBack() - } - style: ButtonStyle { - background: Image { - source: "../back.png" - width: 30 - height: 30 - } - } - } - - TextField { - anchors { - left: back.right - right: toggleInspector.left - leftMargin: 5 - rightMargin: 5 - } - id: uriNav - y: parent.height / 2 - this.height / 2 - - Keys.onReturnPressed: { - webview.url = this.text; - } - } - - Button { - id: toggleInspector - anchors { - right: parent.right - } - iconSource: "../bug.png" - onClicked: { - if(inspector.visible == true){ - inspector.visible = false - }else{ - inspector.visible = true - inspector.url = webview.experimental.remoteInspectorUrl - } - } - } - } - - - WebView { - objectName: "webView" - id: webview - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - top: navBar.bottom - } - onTitleChanged: { window.title = title } - - property var cleanPath: false - onNavigationRequested: { - if(!this.cleanPath) { - var uri = request.url.toString(); - if(!/.*\:\/\/.*/.test(uri)) { - uri = "http://" + uri; - } - - var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/ - - if(reg.test(uri)) { - uri.replace(reg, function(match, pre, domain, path) { - uri = pre; - - var lookup = ui.lookupDomain(domain.substring(0, domain.length - 4)); - var ip = []; - for(var i = 0, l = lookup.length; i < l; i++) { - ip.push(lookup.charCodeAt(i)) - } - - if(ip.length != 0) { - uri += lookup; - } else { - uri += domain; - } - - uri += path; - }); - } - - this.cleanPath = true; - - webview.url = uri; - } else { - // Prevent inf loop. - this.cleanPath = false; - } - } - - - experimental.preferences.javascriptEnabled: true - experimental.preferences.navigatorQtObjectEnabled: true - experimental.preferences.developerExtrasEnabled: true - experimental.userScripts: ["../ext/pre.js", "../ext/big.js", "../ext/string.js", "../ext/ethereum.js"] - experimental.onMessageReceived: { - console.log("[onMessageReceived]: ", message.data) - // TODO move to messaging.js - var data = JSON.parse(message.data) - - try { - switch(data.call) { - case "getCoinBase": - postData(data._seed, eth.coinBase()) - - break - - case "getIsListening": - postData(data._seed, eth.isListening()) - - break - - case "getIsMining": - postData(data._seed, eth.isMining()) - - break - - case "getPeerCount": - postData(data._seed, eth.peerCount()) - - break - - case "getTxCountAt": - require(1) - postData(data._seed, eth.txCountAt(data.args[0])) - - break - - case "getBlockByNumber": - var block = eth.blockByNumber(data.args[0]) - postData(data._seed, block) - - break - - case "getBlockByHash": - var block = eth.blockByHash(data.args[0]) - postData(data._seed, block) - - break - - case "transact": - require(5) - - var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5]) - postData(data._seed, tx) - - break - - case "getStorage": - require(2); - - var stateObject = eth.stateObject(data.args[0]) - var storage = stateObject.storageAt(data.args[1]) - postData(data._seed, storage) - - break - - case "getEachStorage": - require(1); - var storage = JSON.parse(eth.eachStorage(data.args[0])) - postData(data._seed, storage) - - break - - case "getTransactionsFor": - require(1); - var txs = eth.transactionsFor(data.args[0], true) - postData(data._seed, txs) - - break - - case "getBalance": - require(1); - - postData(data._seed, eth.stateObject(data.args[0]).value()); - - break - - case "getKey": - var key = eth.key().privateKey; - - postData(data._seed, key) - break - - /* - case "watch": - require(1) - eth.watch(data.args[0], data.args[1]); - - break - */ - case "watch": - require(2) - eth.watch(data.args[0], data.args[1]) - - case "disconnect": - require(1) - postData(data._seed, null) - - break; - - case "getSecretToAddress": - require(1) - postData(data._seed, eth.secretToAddress(data.args[0])) - - break; - - case "messages": - require(1); - - var messages = JSON.parse(eth.getMessages(data.args[0])) - postData(data._seed, messages) - - break - - case "mutan": - require(1) - - var code = eth.compileMutan(data.args[0]) - postData(data._seed, "0x"+code) - - break; - } - } catch(e) { - console.log(data.call + ": " + e) - - postData(data._seed, null); - } - } - - function post(seed, data) { - console.log("data", data) - postData(data._seed, data) - } - - function require(args, num) { - if(args.length < num) { - throw("required argument count of "+num+" got "+args.length); - } - } - function postData(seed, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) - } - function postEvent(event, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) - } - - function onWatchedCb(data, id) { - var messages = JSON.parse(data) - postEvent("watched:"+id, messages) - } - - function onNewBlockCb(block) { - postEvent("block:new", block) - } - function onObjectChangeCb(stateObject) { - postEvent("object:"+stateObject.address(), stateObject) - } - function onStorageChangeCb(storageObject) { - var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":"); - postEvent(ev, [storageObject.address, storageObject.value]) - } - } - - - Rectangle { - id: sizeGrip - color: "gray" - visible: false - height: 10 - anchors { - left: root.left - right: root.right - } - y: Math.round(root.height * 2 / 3) - - MouseArea { - anchors.fill: parent - drag.target: sizeGrip - drag.minimumY: 0 - drag.maximumY: root.height - drag.axis: Drag.YAxis - } - } - - WebView { - id: inspector - visible: false - anchors { - left: root.left - right: root.right - top: sizeGrip.bottom - bottom: root.bottom - } - } - - states: [ - State { - name: "inspectorShown" - PropertyChanges { - target: inspector - } - } - ] - } -} diff --git a/ethereum/flags.go b/ethereum/flags.go index 5ed208411..c488e6314 100644 --- a/ethereum/flags.go +++ b/ethereum/flags.go @@ -74,6 +74,7 @@ func Init() { flag.IntVar(&LogLevel, "loglevel", int(ethlog.InfoLevel), "loglevel: 0-5: silent,error,warn,info,debug,debug detail)") flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0") flag.StringVar(&DiffType, "diff", "all", "sets the level of diff output [vm, all]. Has no effect if difftool=false") + flag.BoolVar(&ShowGenesis, "genesis", false, "Dump the genesis block") flag.BoolVar(&Dump, "dump", false, "output the ethereum state in JSON format. Sub args [number, hash]") flag.StringVar(&DumpHash, "hash", "", "specify arg in hex") diff --git a/ethereum/main.go b/ethereum/main.go index b7c8ea1e7..df9737c1f 100644 --- a/ethereum/main.go +++ b/ethereum/main.go @@ -13,7 +13,7 @@ import ( const ( ClientIdentifier = "Ethereum(G)" - Version = "0.6.4" + Version = "0.6.5" ) var logger = ethlog.NewLogger("CLI") @@ -40,6 +40,12 @@ func main() { utils.InitLogging(Datadir, LogFile, LogLevel, DebugFile) db := utils.NewDatabase() + err := utils.DBSanityCheck(db) + if err != nil { + logger.Errorln(err) + + os.Exit(1) + } keyManager := utils.NewKeyManager(KeyStore, Datadir, db) @@ -70,6 +76,8 @@ func main() { os.Exit(1) } + fmt.Printf("RLP: %x\nstate: %x\nhash: %x\n", ethutil.Rlp(block), block.GetRoot(), block.Hash()) + // Leave the Println. This needs clean output for piping fmt.Printf("%s\n", block.State().Dump()) diff --git a/javascript/javascript_runtime.go b/javascript/javascript_runtime.go index c794c32a8..ffc672a63 100644 --- a/javascript/javascript_runtime.go +++ b/javascript/javascript_runtime.go @@ -42,7 +42,7 @@ func (jsre *JSRE) LoadExtFile(path string) { } func (jsre *JSRE) LoadIntFile(file string) { - assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "ethereal", "assets", "ext") + assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "mist", "assets", "ext") jsre.LoadExtFile(path.Join(assetPath, file)) } diff --git a/javascript/types.go b/javascript/types.go index afa0a41c6..53a2977a8 100644 --- a/javascript/types.go +++ b/javascript/types.go @@ -88,6 +88,10 @@ func (self *JSEthereum) GetStateObject(addr string) otto.Value { return self.toVal(&JSStateObject{ethpipe.NewJSObject(self.JSPipe.World().SafeGet(ethutil.Hex2Bytes(addr))), self}) } +func (self *JSEthereum) Peers() otto.Value { + return self.toVal(self.JSPipe.Peers()) +} + func (self *JSEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value { r, err := self.JSPipe.Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr) if err != nil { diff --git a/ethereal/assets/back.png b/mist/assets/back.png Binary files differindex 38fc84d6e..38fc84d6e 100644 --- a/ethereal/assets/back.png +++ b/mist/assets/back.png diff --git a/mist/assets/browser.png b/mist/assets/browser.png Binary files differnew file mode 100644 index 000000000..1d7348170 --- /dev/null +++ b/mist/assets/browser.png diff --git a/ethereal/assets/bug.png b/mist/assets/bug.png Binary files differindex f5e85dc99..f5e85dc99 100644 --- a/ethereal/assets/bug.png +++ b/mist/assets/bug.png diff --git a/ethereal/assets/close.png b/mist/assets/close.png Binary files differindex 88df442c5..88df442c5 100644 --- a/ethereal/assets/close.png +++ b/mist/assets/close.png diff --git a/mist/assets/debugger/debugger.qml b/mist/assets/debugger/debugger.qml new file mode 100644 index 000000000..8d54b5b5d --- /dev/null +++ b/mist/assets/debugger/debugger.qml @@ -0,0 +1,435 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +ApplicationWindow { + id: win + visible: false + title: "IceCREAM" + minimumWidth: 1280 + minimumHeight: 700 + width: 1290 + height: 750 + + property alias codeText: codeEditor.text + property alias dataText: rawDataField.text + + onClosing: { + dbg.Stop() + } + + menuBar: MenuBar { + Menu { + title: "Edit" + MenuItem { + text: "Focus code" + shortcut: "Ctrl+1" + onTriggered: { + codeEditor.focus = true + } + } + MenuItem { + text: "Focus data" + shortcut: "Ctrl+2" + onTriggered: { + rawDataField.focus = true + } + } + + MenuItem { + text: "Command" + shortcut: "Ctrl+l" + onTriggered: { + dbgCommand.focus = true + } + } + } + + Menu { + title: "Debugger" + MenuItem { + text: "Run" + shortcut: "Ctrl+r" + onTriggered: debugCurrent() + } + + MenuItem { + text: "Stop" + onTriggered: dbp.stop() + } + + MenuSeparator {} + + MenuItem { + text: "Next" + shortcut: "Ctrl+n" + onTriggered: dbg.next() + } + + MenuItem { + text: "Continue" + shortcut: "Ctrl+g" + onTriggered: dbg.continue() + } + } + } + + + SplitView { + anchors.fill: parent + property var asmModel: ListModel { + id: asmModel + } + + TableView { + id: asmTableView + width: 200 + headerVisible: false + TableViewColumn{ role: "value" ; title: "" ; width: asmTableView.width - 2 } + model: asmModel + /* + alternatingRowColors: false + itemDelegate: Item { + Rectangle { + anchors.fill: parent + color: "#DDD" + Text { + anchors { + left: parent.left + right: parent.right + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + color: "#333" + elide: styleData.elideMode + text: styleData.value + font.pixelSize: 11 + MouseArea { + acceptedButtons: Qt.LeftButton + anchors.fill: parent + onClicked: { + mouse.accepted = true + } + } + } + } + } + */ + } + + Rectangle { + color: "#00000000" + anchors.left: asmTableView.right + anchors.right: parent.right + SplitView { + orientation: Qt.Vertical + anchors.fill: parent + + Rectangle { + color: "#00000000" + height: 330 + anchors.left: parent.left + anchors.right: parent.right + + TextArea { + id: codeEditor + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: settings.left + focus: true + + /* + Timer { + id: compileTimer + interval: 500 ; running: true ; repeat: true + onTriggered: { + dbg.autoComp(codeEditor.text) + } + } + */ + } + + Column { + id: settings + spacing: 5 + width: 300 + height: parent.height + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + Label { + text: "Arbitrary data" + } + TextArea { + id: rawDataField + anchors.left: parent.left + anchors.right: parent.right + height: 150 + } + + Label { + text: "Amount" + } + TextField { + id: txValue + width: 200 + placeholderText: "Amount" + validator: RegExpValidator { regExp: /\d*/ } + } + Label { + text: "Amount of gas" + } + TextField { + id: txGas + width: 200 + validator: RegExpValidator { regExp: /\d*/ } + text: "10000" + placeholderText: "Gas" + } + Label { + text: "Gas price" + } + TextField { + id: txGasPrice + width: 200 + placeholderText: "Gas price" + text: "1000000000000" + validator: RegExpValidator { regExp: /\d*/ } + } + } + } + + SplitView { + orientation: Qt.Vertical + id: inspectorPane + height: 500 + + SplitView { + orientation: Qt.Horizontal + height: 150 + + TableView { + id: stackTableView + property var stackModel: ListModel { + id: stackModel + } + height: parent.height + width: 300 + TableViewColumn{ role: "value" ; title: "Local VM stack" ; width: stackTableView.width - 2 } + model: stackModel + } + + TableView { + id: memoryTableView + property var memModel: ListModel { + id: memModel + } + height: parent.height + width: parent.width - stackTableView.width + TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50 } + TableViewColumn{ role: "value" ; title: "Memory" ; width: 650 } + model: memModel + } + } + + Rectangle { + height: 100 + width: parent.width + TableView { + id: storageTableView + property var memModel: ListModel { + id: storageModel + } + height: parent.height + width: parent.width + TableViewColumn{ id: key ; role: "key" ; title: "#" ; width: storageTableView.width / 2 - 1} + TableViewColumn{ role: "value" ; title: "Storage" ; width: storageTableView.width / 2 - 1} + model: storageModel + } + } + + Rectangle { + height: 200 + width: parent.width * 0.66 + TableView { + id: logTableView + property var logModel: ListModel { + id: logModel + } + height: parent.height + width: parent.width + TableViewColumn{ id: message ; role: "message" ; title: "log" ; width: logTableView.width - 2 } + model: logModel + } + } + + } + } + } + } + + function exec() { + dbg.execCommand(dbgCommand.text); + dbgCommand.text = ""; + } + statusBar: StatusBar { + height: 30 + + + TextField { + id: dbgCommand + y: 1 + x: asmTableView.width + width: 500 + placeholderText: "Debugger (type 'help')" + Keys.onReturnPressed: { + exec() + } + } + + RowLayout { + anchors.left: dbgCommand.right + anchors.leftMargin: 10 + spacing: 5 + y: parent.height / 2 - this.height / 2 + + Text { + objectName: "stackFrame" + font.pixelSize: 10 + text: "<b>stack ptr</b>: 0" + } + + Text { + objectName: "stackSize" + font.pixelSize: 10 + text: "<b>stack size</b>: 0" + } + + Text { + objectName: "memSize" + font.pixelSize: 10 + text: "<b>mem size</b>: 0" + } + } + } + + toolBar: ToolBar { + height: 30 + RowLayout { + spacing: 10 + + Button { + property var enabled: true + id: debugStart + onClicked: { + debugCurrent() + } + text: "Debug" + } + + Button { + property var enabled: true + id: debugNextButton + onClicked: { + dbg.next() + } + text: "Next" + } + + Button { + id: debugContinueButton + onClicked: { + dbg.continue() + } + text: "Continue" + } + } + + + ComboBox { + id: snippets + anchors.right: parent.right + model: ListModel { + ListElement { text: "Snippets" ; value: "" } + ListElement { text: "Call Contract" ; value: "var[2] in;\nvar ret;\n\nin[0] = \"arg1\"\nin[1] = 0xdeadbeef\n\nvar success = call(0x0c542ddea93dae0c2fcb2cf175f03ad80d6be9a0, 0, 7000, in, ret)\n\nreturn ret" } + } + onCurrentIndexChanged: { + if(currentIndex != 0) { + var code = snippets.model.get(currentIndex).value; + codeEditor.insert(codeEditor.cursorPosition, code) + } + } + } + + } + + function debugCurrent() { + dbg.debug(txValue.text, txGas.text, txGasPrice.text, codeEditor.text, rawDataField.text) + } + + function setAsm(asm) { + asmModel.append({asm: asm}) + } + + function clearAsm() { + asmModel.clear() + } + + function setInstruction(num) { + asmTableView.selection.clear() + asmTableView.selection.select(num) + asmTableView.positionViewAtRow(num, ListView.Center) + } + + function setMem(mem) { + memModel.append({num: mem.num, value: mem.value}) + } + function clearMem(){ + memModel.clear() + } + + function setStack(stack) { + stackModel.append({value: stack}) + } + function addDebugMessage(message){ + debuggerLog.append({value: message}) + } + + function clearStack() { + stackModel.clear() + } + + function clearStorage() { + storageModel.clear() + } + + function setStorage(storage) { + storageModel.append({key: storage.key, value: storage.value}) + } + + function setLog(msg) { + // Remove first item once we've reached max log items + if(logModel.count > 250) { + logModel.remove(0) + } + + if(msg.len != 0) { + if(logTableView.flickableItem.atYEnd) { + logModel.append({message: msg}) + logTableView.positionViewAtRow(logTableView.rowCount - 1, ListView.Contain) + } else { + logModel.append({message: msg}) + } + } + } + + function clearLog() { + logModel.clear() + } +} diff --git a/ethereal/assets/ext/big.js b/mist/assets/ext/big.js index db633fd2f..db633fd2f 100644 --- a/ethereal/assets/ext/big.js +++ b/mist/assets/ext/big.js diff --git a/ethereal/assets/ext/ethereum.js b/mist/assets/ext/ethereum.js index 697a404a3..697a404a3 100644 --- a/ethereal/assets/ext/ethereum.js +++ b/mist/assets/ext/ethereum.js diff --git a/mist/assets/ext/filter.js b/mist/assets/ext/filter.js new file mode 100644 index 000000000..c23706249 --- /dev/null +++ b/mist/assets/ext/filter.js @@ -0,0 +1,49 @@ +var ethx = { + prototype: Object, + + watch: function(options) { + return new Filter(options); + }, + + note: function() { + var args = Array.prototype.slice.call(arguments, 0); + var o = [] + for(var i = 0; i < args.length; i++) { + o.push(args[i].toString()) + } + + eth.notef(o); + }, +}; + +var Filter = function(options) { + this.callbacks = []; + this.options = options; + + if(options === "chain") { + this.id = eth.newFilterString(options); + } else if(typeof options === "object") { + this.id = eth.newFilter(options); + } +}; + +Filter.prototype.changed = function(callback) { + this.callbacks.push(callback); + + var self = this; + messages.connect(function(messages, id) { + if(id == self.id) { + for(var i = 0; i < self.callbacks.length; i++) { + self.callbacks[i].call(self, messages); + } + } + }); +}; + +Filter.prototype.uninstall = function() { + eth.uninstallFilter(this.id) +} + +Filter.prototype.messages = function() { + return eth.messages(this.id) +} diff --git a/ethereal/assets/ext/home.html b/mist/assets/ext/home.html index a524e8403..a524e8403 100644 --- a/ethereal/assets/ext/home.html +++ b/mist/assets/ext/home.html diff --git a/mist/assets/ext/html_messaging.js b/mist/assets/ext/html_messaging.js new file mode 100644 index 000000000..3c67c77ea --- /dev/null +++ b/mist/assets/ext/html_messaging.js @@ -0,0 +1,481 @@ +// The magic return variable. The magic return variable will be set during the execution of the QML call. +(function(window) { + function message(type, data) { + document.title = JSON.stringify({type: type, data: data}); + + return window.____returnData; + } + + function isPromise(o) { + return typeof o === "object" && o.then + } + + window.eth = { + _callbacks: {}, + _events: {}, + prototype: Object(), + + toHex: function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var n = str.charCodeAt(i).toString(16); + hex += n.length < 2 ? '0' + n : n; + } + + return hex; + }, + + toAscii: function(hex) { + // Find termination + var str = ""; + var i = 0, l = hex.length; + for(; i < l; i+=2) { + var code = hex.charCodeAt(i) + if(code == 0) { + break; + } + + str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + + return str; + }, + + fromAscii: function(str, pad) { + if(pad === undefined) { + pad = 32 + } + + var hex = this.toHex(str); + + while(hex.length < pad*2) + hex += "00"; + + return hex + }, + + block: function(numberOrHash) { + return new Promise(function(resolve, reject) { + var func; + if(typeof numberOrHash == "string") { + func = "getBlockByHash"; + } else { + func = "getBlockByNumber"; + } + + postData({call: func, args: [numberOrHash]}, function(block) { + if(block) + resolve(block); + else + reject("not found"); + + }); + }); + }, + + transact: function(params) { + if(params === undefined) { + params = {}; + } + + if(params.endowment !== undefined) + params.value = params.endowment; + if(params.code !== undefined) + params.data = params.code; + + + var promises = [] + if(isPromise(params.to)) { + promises.push(params.to.then(function(_to) { params.to = _to; })); + } + if(isPromise(params.from)) { + promises.push(params.from.then(function(_from) { params.from = _from; })); + } + + if(isPromise(params.data)) { + promises.push(params.data.then(function(_code) { params.data = _code; })); + } else { + if(typeof params.data === "object") { + data = ""; + for(var i = 0; i < params.data.length; i++) { + data += params.data[i] + } + } else { + data = params.data; + } + } + + // Make sure everything is string + var fields = ["value", "gas", "gasPrice"]; + for(var i = 0; i < fields.length; i++) { + if(params[fields[i]] === undefined) { + params[fields[i]] = ""; + } + params[fields[i]] = params[fields[i]].toString(); + } + + // Load promises then call the last "transact". + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "transact", args: params}, function(data) { + if(data[1]) + reject(data[0]); + else + resolve(data[0]); + }); + }); + }) + }, + + compile: function(code) { + return new Promise(function(resolve, reject) { + postData({call: "compile", args: [code]}, function(data) { + if(data[1]) + reject(data[0]); + else + resolve(data[0]); + }); + }); + }, + + balanceAt: function(address) { + var promises = []; + + if(isPromise(address)) { + promises.push(address.then(function(_address) { address = _address; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getBalanceAt", args: [address]}, function(balance) { + resolve(balance); + }); + }); + }); + }, + + countAt: function(address) { + var promises = []; + + if(isPromise(address)) { + promises.push(address.then(function(_address) { address = _address; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getCountAt", args: [address]}, function(count) { + resolve(count); + }); + }); + }); + }, + + codeAt: function(address) { + var promises = []; + + if(isPromise(address)) { + promises.push(address.then(function(_address) { address = _address; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getCodeAt", args: [address]}, function(code) { + resolve(code); + }); + }); + }); + }, + + storageAt: function(address, storageAddress) { + var promises = []; + + if(isPromise(address)) { + promises.push(address.then(function(_address) { address = _address; })); + } + + if(isPromise(storageAddress)) { + promises.push(storageAddress.then(function(_sa) { storageAddress = _sa; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getStorageAt", args: [address, storageAddress]}, function(entry) { + resolve(entry); + }); + }); + }); + }, + + stateAt: function(address, storageAddress) { + return this.storageAt(address, storageAddress); + }, + + call: function(params) { + if(params === undefined) { + params = {}; + } + + if(params.endowment !== undefined) + params.value = params.endowment; + if(params.code !== undefined) + params.data = params.code; + + + var promises = [] + if(isPromise(params.to)) { + promises.push(params.to.then(function(_to) { params.to = _to; })); + } + if(isPromise(params.from)) { + promises.push(params.from.then(function(_from) { params.from = _from; })); + } + + if(isPromise(params.data)) { + promises.push(params.data.then(function(_code) { params.data = _code; })); + } else { + if(typeof params.data === "object") { + data = ""; + for(var i = 0; i < params.data.length; i++) { + data += params.data[i] + } + } else { + data = params.data; + } + } + + // Make sure everything is string + var fields = ["value", "gas", "gasPrice"]; + for(var i = 0; i < fields.length; i++) { + if(params[fields[i]] === undefined) { + params[fields[i]] = ""; + } + params[fields[i]] = params[fields[i]].toString(); + } + + // Load promises then call the last "transact". + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "call", args: params}, function(data) { + if(data[1]) + reject(data[0]); + else + resolve(data[0]); + }); + }); + }) + }, + + watch: function(params) { + return new Filter(params); + }, + + secretToAddress: function(key) { + var promises = []; + if(isPromise(key)) { + promises.push(key.then(function(_key) { key = _key; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getSecretToAddress", args: [key]}, function(address) { + resolve(address); + }); + }); + }); + }, + + on: function(event, cb) { + if(eth._events[event] === undefined) { + eth._events[event] = []; + } + + eth._events[event].push(cb); + + return this + }, + + off: function(event, cb) { + if(eth._events[event] !== undefined) { + var callbacks = eth._events[event]; + for(var i = 0; i < callbacks.length; i++) { + if(callbacks[i] === cb) { + delete callbacks[i]; + } + } + } + + return this + }, + + trigger: function(event, data) { + var callbacks = eth._events[event]; + if(callbacks !== undefined) { + for(var i = 0; i < callbacks.length; i++) { + // Figure out whether the returned data was an array + // array means multiple return arguments (multiple params) + if(data instanceof Array) { + callbacks[i].apply(this, data); + } else { + callbacks[i].call(this, data); + } + } + } + }, + }; + + // Eth object properties + Object.defineProperty(eth, "key", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getKey"}, function(k) { + resolve(k); + }); + }); + }, + }); + + Object.defineProperty(eth, "gasPrice", { + get: function() { + return "1000000000000" + } + }); + + Object.defineProperty(eth, "coinbase", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getCoinBase"}, function(coinbase) { + resolve(coinbase); + }); + }); + }, + }); + + Object.defineProperty(eth, "listening", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getIsListening"}, function(listening) { + resolve(listening); + }); + }); + }, + }); + + + Object.defineProperty(eth, "mining", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getIsMining"}, function(mining) { + resolve(mining); + }); + }); + }, + }); + + Object.defineProperty(eth, "peerCount", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getPeerCount"}, function(peerCount) { + resolve(peerCount); + }); + }); + }, + }); + + var filters = []; + var Filter = function(options) { + filters.push(this); + + this.callbacks = []; + this.options = options; + + var call; + if(options === "chain") { + call = "newFilterString" + } else if(typeof options === "object") { + call = "newFilter" + } + + var self = this; // Cheaper than binding + this.promise = new Promise(function(resolve, reject) { + postData({call: call, args: [options]}, function(id) { + self.id = id; + + resolve(id); + }); + }); + }; + + Filter.prototype.changed = function(callback) { + var self = this; + this.promise.then(function(id) { + self.callbacks.push(callback); + }); + }; + + Filter.prototype.trigger = function(messages, id) { + if(id == this.id) { + for(var i = 0; i < this.callbacks.length; i++) { + this.callbacks[i].call(this, messages); + } + } + }; + + Filter.prototype.uninstall = function() { + this.promise.then(function(id) { + postData({call: "uninstallFilter", args:[id]}); + }); + }; + + Filter.prototype.messages = function() { + var self=this; + return Q.all([this.promise]).then(function() { + var id = self.id + return new Promise(function(resolve, reject) { + postData({call: "getMessages", args: [id]}, function(messages) { + resolve(messages); + }); + }); + }); + }; + + // Register to the messages callback. "messages" will be emitted when new messages + // from the client have been created. + eth.on("messages", function(messages, id) { + for(var i = 0; i < filters.length; i++) { + filters[i].trigger(messages, id); + } + }); + + var g_seed = 1; + function postData(data, cb) { + data._seed = g_seed; + if(cb) { + eth._callbacks[data._seed] = cb; + } + + if(data.args === undefined) { + data.args = []; + } + + g_seed++; + + navigator.qt.postMessage(JSON.stringify(data)); + } + + navigator.qt.onmessage = function(ev) { + var data = JSON.parse(ev.data) + + if(data._event !== undefined) { + eth.trigger(data._event, data.data); + } else { + if(data._seed) { + var cb = eth._callbacks[data._seed]; + if(cb) { + cb.call(this, data.data) + + // Remove the "trigger" callback + delete eth._callbacks[ev._seed]; + } + } + } + } +})(this); diff --git a/mist/assets/ext/http.js b/mist/assets/ext/http.js new file mode 100644 index 000000000..725ce8e6b --- /dev/null +++ b/mist/assets/ext/http.js @@ -0,0 +1,13 @@ +// this function is included locally, but you can also include separately via a header definition +function request(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = (function(req) { + return function() { + if(req.readyState === 4) { + callback(req); + } + } + })(xhr); + xhr.open('GET', url, true); + xhr.send(''); +} diff --git a/mist/assets/ext/pre.js b/mist/assets/ext/pre.js new file mode 100644 index 000000000..f298fe9a1 --- /dev/null +++ b/mist/assets/ext/pre.js @@ -0,0 +1,3 @@ +if(typeof(Promise) === "undefined") { + window.Promise = Q.Promise; +} diff --git a/mist/assets/ext/q.js b/mist/assets/ext/q.js new file mode 100644 index 000000000..23c4245ee --- /dev/null +++ b/mist/assets/ext/q.js @@ -0,0 +1,1909 @@ +// vim:ts=4:sts=4:sw=4: +/*! + * + * Copyright 2009-2012 Kris Kowal under the terms of the MIT + * license found at http://github.com/kriskowal/q/raw/master/LICENSE + * + * With parts by Tyler Close + * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found + * at http://www.opensource.org/licenses/mit-license.html + * Forked at ref_send.js version: 2009-05-11 + * + * With parts by Mark Miller + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +(function (definition) { + // Turn off strict mode for this function so we can assign to global.Q + /* jshint strict: false */ + + // This file will function properly as a <script> tag, or a module + // using CommonJS and NodeJS or RequireJS module formats. In + // Common/Node/RequireJS, the module exports the Q API and when + // executed as a simple <script>, it creates a Q global instead. + + // Montage Require + if (typeof bootstrap === "function") { + bootstrap("promise", definition); + + // CommonJS + } else if (typeof exports === "object" && typeof module === "object") { + module.exports = definition(); + + // RequireJS + } else if (typeof define === "function" && define.amd) { + define(definition); + + // SES (Secure EcmaScript) + } else if (typeof ses !== "undefined") { + if (!ses.ok()) { + return; + } else { + ses.makeQ = definition; + } + + // <script> + } else { + Q = definition(); + } + +})(function () { +"use strict"; + +var hasStacks = false; +try { + throw new Error(); +} catch (e) { + hasStacks = !!e.stack; +} + +// All code after this point will be filtered from stack traces reported +// by Q. +var qStartingLine = captureLine(); +var qFileName; + +// shims + +// used for fallback in "allResolved" +var noop = function () {}; + +// Use the fastest possible means to execute a task in a future turn +// of the event loop. +var nextTick =(function () { + // linked list of tasks (single, with head node) + var head = {task: void 0, next: null}; + var tail = head; + var flushing = false; + var requestTick = void 0; + var isNodeJS = false; + + function flush() { + /* jshint loopfunc: true */ + + while (head.next) { + head = head.next; + var task = head.task; + head.task = void 0; + var domain = head.domain; + + if (domain) { + head.domain = void 0; + domain.enter(); + } + + try { + task(); + + } catch (e) { + if (isNodeJS) { + // In node, uncaught exceptions are considered fatal errors. + // Re-throw them synchronously to interrupt flushing! + + // Ensure continuation if the uncaught exception is suppressed + // listening "uncaughtException" events (as domains does). + // Continue in next event to avoid tick recursion. + if (domain) { + domain.exit(); + } + setTimeout(flush, 0); + if (domain) { + domain.enter(); + } + + throw e; + + } else { + // In browsers, uncaught exceptions are not fatal. + // Re-throw them asynchronously to avoid slow-downs. + setTimeout(function() { + throw e; + }, 0); + } + } + + if (domain) { + domain.exit(); + } + } + + flushing = false; + } + + nextTick = function (task) { + tail = tail.next = { + task: task, + domain: isNodeJS && process.domain, + next: null + }; + + if (!flushing) { + flushing = true; + requestTick(); + } + }; + + if (typeof process !== "undefined" && process.nextTick) { + // Node.js before 0.9. Note that some fake-Node environments, like the + // Mocha test runner, introduce a `process` global without a `nextTick`. + isNodeJS = true; + + requestTick = function () { + process.nextTick(flush); + }; + + } else if (typeof setImmediate === "function") { + // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate + if (typeof window !== "undefined") { + requestTick = setImmediate.bind(window, flush); + } else { + requestTick = function () { + setImmediate(flush); + }; + } + + } else if (typeof MessageChannel !== "undefined") { + // modern browsers + // http://www.nonblocking.io/2011/06/windownexttick.html + var channel = new MessageChannel(); + // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create + // working message ports the first time a page loads. + channel.port1.onmessage = function () { + requestTick = requestPortTick; + channel.port1.onmessage = flush; + flush(); + }; + var requestPortTick = function () { + // Opera requires us to provide a message payload, regardless of + // whether we use it. + channel.port2.postMessage(0); + }; + requestTick = function () { + setTimeout(flush, 0); + requestPortTick(); + }; + + } else { + // old browsers + requestTick = function () { + setTimeout(flush, 0); + }; + } + + return nextTick; +})(); + +// Attempt to make generics safe in the face of downstream +// modifications. +// There is no situation where this is necessary. +// If you need a security guarantee, these primordials need to be +// deeply frozen anyway, and if you don’t need a security guarantee, +// this is just plain paranoid. +// However, this **might** have the nice side-effect of reducing the size of +// the minified code by reducing x.call() to merely x() +// See Mark Miller’s explanation of what this does. +// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming +var call = Function.call; +function uncurryThis(f) { + return function () { + return call.apply(f, arguments); + }; +} +// This is equivalent, but slower: +// uncurryThis = Function_bind.bind(Function_bind.call); +// http://jsperf.com/uncurrythis + +var array_slice = uncurryThis(Array.prototype.slice); + +var array_reduce = uncurryThis( + Array.prototype.reduce || function (callback, basis) { + var index = 0, + length = this.length; + // concerning the initial value, if one is not provided + if (arguments.length === 1) { + // seek to the first value in the array, accounting + // for the possibility that is is a sparse array + do { + if (index in this) { + basis = this[index++]; + break; + } + if (++index >= length) { + throw new TypeError(); + } + } while (1); + } + // reduce + for (; index < length; index++) { + // account for the possibility that the array is sparse + if (index in this) { + basis = callback(basis, this[index], index); + } + } + return basis; + } +); + +var array_indexOf = uncurryThis( + Array.prototype.indexOf || function (value) { + // not a very good shim, but good enough for our one use of it + for (var i = 0; i < this.length; i++) { + if (this[i] === value) { + return i; + } + } + return -1; + } +); + +var array_map = uncurryThis( + Array.prototype.map || function (callback, thisp) { + var self = this; + var collect = []; + array_reduce(self, function (undefined, value, index) { + collect.push(callback.call(thisp, value, index, self)); + }, void 0); + return collect; + } +); + +var object_create = Object.create || function (prototype) { + function Type() { } + Type.prototype = prototype; + return new Type(); +}; + +var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty); + +var object_keys = Object.keys || function (object) { + var keys = []; + for (var key in object) { + if (object_hasOwnProperty(object, key)) { + keys.push(key); + } + } + return keys; +}; + +var object_toString = uncurryThis(Object.prototype.toString); + +function isObject(value) { + return value === Object(value); +} + +// generator related shims + +// FIXME: Remove this function once ES6 generators are in SpiderMonkey. +function isStopIteration(exception) { + return ( + object_toString(exception) === "[object StopIteration]" || + exception instanceof QReturnValue + ); +} + +// FIXME: Remove this helper and Q.return once ES6 generators are in +// SpiderMonkey. +var QReturnValue; +if (typeof ReturnValue !== "undefined") { + QReturnValue = ReturnValue; +} else { + QReturnValue = function (value) { + this.value = value; + }; +} + +// long stack traces + +var STACK_JUMP_SEPARATOR = "From previous event:"; + +function makeStackTraceLong(error, promise) { + // If possible, transform the error stack trace by removing Node and Q + // cruft, then concatenating with the stack trace of `promise`. See #57. + if (hasStacks && + promise.stack && + typeof error === "object" && + error !== null && + error.stack && + error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1 + ) { + var stacks = []; + for (var p = promise; !!p; p = p.source) { + if (p.stack) { + stacks.unshift(p.stack); + } + } + stacks.unshift(error.stack); + + var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n"); + error.stack = filterStackString(concatedStacks); + } +} + +function filterStackString(stackString) { + var lines = stackString.split("\n"); + var desiredLines = []; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + + if (!isInternalFrame(line) && !isNodeFrame(line) && line) { + desiredLines.push(line); + } + } + return desiredLines.join("\n"); +} + +function isNodeFrame(stackLine) { + return stackLine.indexOf("(module.js:") !== -1 || + stackLine.indexOf("(node.js:") !== -1; +} + +function getFileNameAndLineNumber(stackLine) { + // Named functions: "at functionName (filename:lineNumber:columnNumber)" + // In IE10 function name can have spaces ("Anonymous function") O_o + var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine); + if (attempt1) { + return [attempt1[1], Number(attempt1[2])]; + } + + // Anonymous functions: "at filename:lineNumber:columnNumber" + var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine); + if (attempt2) { + return [attempt2[1], Number(attempt2[2])]; + } + + // Firefox style: "function@filename:lineNumber or @filename:lineNumber" + var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine); + if (attempt3) { + return [attempt3[1], Number(attempt3[2])]; + } +} + +function isInternalFrame(stackLine) { + var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine); + + if (!fileNameAndLineNumber) { + return false; + } + + var fileName = fileNameAndLineNumber[0]; + var lineNumber = fileNameAndLineNumber[1]; + + return fileName === qFileName && + lineNumber >= qStartingLine && + lineNumber <= qEndingLine; +} + +// discover own file name and line number range for filtering stack +// traces +function captureLine() { + if (!hasStacks) { + return; + } + + try { + throw new Error(); + } catch (e) { + var lines = e.stack.split("\n"); + var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2]; + var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine); + if (!fileNameAndLineNumber) { + return; + } + + qFileName = fileNameAndLineNumber[0]; + return fileNameAndLineNumber[1]; + } +} + +function deprecate(callback, name, alternative) { + return function () { + if (typeof console !== "undefined" && + typeof console.warn === "function") { + console.warn(name + " is deprecated, use " + alternative + + " instead.", new Error("").stack); + } + return callback.apply(callback, arguments); + }; +} + +// end of shims +// beginning of real work + +/** + * Constructs a promise for an immediate reference, passes promises through, or + * coerces promises from different systems. + * @param value immediate reference or promise + */ +function Q(value) { + // If the object is already a Promise, return it directly. This enables + // the resolve function to both be used to created references from objects, + // but to tolerably coerce non-promises to promises. + if (value instanceof Promise) { + return value; + } + + // assimilate thenables + if (isPromiseAlike(value)) { + return coerce(value); + } else { + return fulfill(value); + } +} +Q.resolve = Q; + +/** + * Performs a task in a future turn of the event loop. + * @param {Function} task + */ +Q.nextTick = nextTick; + +/** + * Controls whether or not long stack traces will be on + */ +Q.longStackSupport = false; + +/** + * Constructs a {promise, resolve, reject} object. + * + * `resolve` is a callback to invoke with a more resolved value for the + * promise. To fulfill the promise, invoke `resolve` with any value that is + * not a thenable. To reject the promise, invoke `resolve` with a rejected + * thenable, or invoke `reject` with the reason directly. To resolve the + * promise to another thenable, thus putting it in the same state, invoke + * `resolve` with that other thenable. + */ +Q.defer = defer; +function defer() { + // if "messages" is an "Array", that indicates that the promise has not yet + // been resolved. If it is "undefined", it has been resolved. Each + // element of the messages array is itself an array of complete arguments to + // forward to the resolved promise. We coerce the resolution value to a + // promise using the `resolve` function because it handles both fully + // non-thenable values and other thenables gracefully. + var messages = [], progressListeners = [], resolvedPromise; + + var deferred = object_create(defer.prototype); + var promise = object_create(Promise.prototype); + + promise.promiseDispatch = function (resolve, op, operands) { + var args = array_slice(arguments); + if (messages) { + messages.push(args); + if (op === "when" && operands[1]) { // progress operand + progressListeners.push(operands[1]); + } + } else { + nextTick(function () { + resolvedPromise.promiseDispatch.apply(resolvedPromise, args); + }); + } + }; + + // XXX deprecated + promise.valueOf = function () { + if (messages) { + return promise; + } + var nearerValue = nearer(resolvedPromise); + if (isPromise(nearerValue)) { + resolvedPromise = nearerValue; // shorten chain + } + return nearerValue; + }; + + promise.inspect = function () { + if (!resolvedPromise) { + return { state: "pending" }; + } + return resolvedPromise.inspect(); + }; + + if (Q.longStackSupport && hasStacks) { + try { + throw new Error(); + } catch (e) { + // NOTE: don't try to use `Error.captureStackTrace` or transfer the + // accessor around; that causes memory leaks as per GH-111. Just + // reify the stack trace as a string ASAP. + // + // At the same time, cut off the first line; it's always just + // "[object Promise]\n", as per the `toString`. + promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1); + } + } + + // NOTE: we do the checks for `resolvedPromise` in each method, instead of + // consolidating them into `become`, since otherwise we'd create new + // promises with the lines `become(whatever(value))`. See e.g. GH-252. + + function become(newPromise) { + resolvedPromise = newPromise; + promise.source = newPromise; + + array_reduce(messages, function (undefined, message) { + nextTick(function () { + newPromise.promiseDispatch.apply(newPromise, message); + }); + }, void 0); + + messages = void 0; + progressListeners = void 0; + } + + deferred.promise = promise; + deferred.resolve = function (value) { + if (resolvedPromise) { + return; + } + + become(Q(value)); + }; + + deferred.fulfill = function (value) { + if (resolvedPromise) { + return; + } + + become(fulfill(value)); + }; + deferred.reject = function (reason) { + if (resolvedPromise) { + return; + } + + become(reject(reason)); + }; + deferred.notify = function (progress) { + if (resolvedPromise) { + return; + } + + array_reduce(progressListeners, function (undefined, progressListener) { + nextTick(function () { + progressListener(progress); + }); + }, void 0); + }; + + return deferred; +} + +/** + * Creates a Node-style callback that will resolve or reject the deferred + * promise. + * @returns a nodeback + */ +defer.prototype.makeNodeResolver = function () { + var self = this; + return function (error, value) { + if (error) { + self.reject(error); + } else if (arguments.length > 2) { + self.resolve(array_slice(arguments, 1)); + } else { + self.resolve(value); + } + }; +}; + +/** + * @param resolver {Function} a function that returns nothing and accepts + * the resolve, reject, and notify functions for a deferred. + * @returns a promise that may be resolved with the given resolve and reject + * functions, or rejected by a thrown exception in resolver + */ +Q.Promise = promise; // ES6 +Q.promise = promise; +function promise(resolver) { + if (typeof resolver !== "function") { + throw new TypeError("resolver must be a function."); + } + var deferred = defer(); + try { + resolver(deferred.resolve, deferred.reject, deferred.notify); + } catch (reason) { + deferred.reject(reason); + } + return deferred.promise; +} + +promise.race = race; // ES6 +promise.all = all; // ES6 +promise.reject = reject; // ES6 +promise.resolve = Q; // ES6 + +// XXX experimental. This method is a way to denote that a local value is +// serializable and should be immediately dispatched to a remote upon request, +// instead of passing a reference. +Q.passByCopy = function (object) { + //freeze(object); + //passByCopies.set(object, true); + return object; +}; + +Promise.prototype.passByCopy = function () { + //freeze(object); + //passByCopies.set(object, true); + return this; +}; + +/** + * If two promises eventually fulfill to the same value, promises that value, + * but otherwise rejects. + * @param x {Any*} + * @param y {Any*} + * @returns {Any*} a promise for x and y if they are the same, but a rejection + * otherwise. + * + */ +Q.join = function (x, y) { + return Q(x).join(y); +}; + +Promise.prototype.join = function (that) { + return Q([this, that]).spread(function (x, y) { + if (x === y) { + // TODO: "===" should be Object.is or equiv + return x; + } else { + throw new Error("Can't join: not the same: " + x + " " + y); + } + }); +}; + +/** + * Returns a promise for the first of an array of promises to become fulfilled. + * @param answers {Array[Any*]} promises to race + * @returns {Any*} the first promise to be fulfilled + */ +Q.race = race; +function race(answerPs) { + return promise(function(resolve, reject) { + // Switch to this once we can assume at least ES5 + // answerPs.forEach(function(answerP) { + // Q(answerP).then(resolve, reject); + // }); + // Use this in the meantime + for (var i = 0, len = answerPs.length; i < len; i++) { + Q(answerPs[i]).then(resolve, reject); + } + }); +} + +Promise.prototype.race = function () { + return this.then(Q.race); +}; + +/** + * Constructs a Promise with a promise descriptor object and optional fallback + * function. The descriptor contains methods like when(rejected), get(name), + * set(name, value), post(name, args), and delete(name), which all + * return either a value, a promise for a value, or a rejection. The fallback + * accepts the operation name, a resolver, and any further arguments that would + * have been forwarded to the appropriate method above had a method been + * provided with the proper name. The API makes no guarantees about the nature + * of the returned object, apart from that it is usable whereever promises are + * bought and sold. + */ +Q.makePromise = Promise; +function Promise(descriptor, fallback, inspect) { + if (fallback === void 0) { + fallback = function (op) { + return reject(new Error( + "Promise does not support operation: " + op + )); + }; + } + if (inspect === void 0) { + inspect = function () { + return {state: "unknown"}; + }; + } + + var promise = object_create(Promise.prototype); + + promise.promiseDispatch = function (resolve, op, args) { + var result; + try { + if (descriptor[op]) { + result = descriptor[op].apply(promise, args); + } else { + result = fallback.call(promise, op, args); + } + } catch (exception) { + result = reject(exception); + } + if (resolve) { + resolve(result); + } + }; + + promise.inspect = inspect; + + // XXX deprecated `valueOf` and `exception` support + if (inspect) { + var inspected = inspect(); + if (inspected.state === "rejected") { + promise.exception = inspected.reason; + } + + promise.valueOf = function () { + var inspected = inspect(); + if (inspected.state === "pending" || + inspected.state === "rejected") { + return promise; + } + return inspected.value; + }; + } + + return promise; +} + +Promise.prototype.toString = function () { + return "[object Promise]"; +}; + +Promise.prototype.then = function (fulfilled, rejected, progressed) { + var self = this; + var deferred = defer(); + var done = false; // ensure the untrusted promise makes at most a + // single call to one of the callbacks + + function _fulfilled(value) { + try { + return typeof fulfilled === "function" ? fulfilled(value) : value; + } catch (exception) { + return reject(exception); + } + } + + function _rejected(exception) { + if (typeof rejected === "function") { + makeStackTraceLong(exception, self); + try { + return rejected(exception); + } catch (newException) { + return reject(newException); + } + } + return reject(exception); + } + + function _progressed(value) { + return typeof progressed === "function" ? progressed(value) : value; + } + + nextTick(function () { + self.promiseDispatch(function (value) { + if (done) { + return; + } + done = true; + + deferred.resolve(_fulfilled(value)); + }, "when", [function (exception) { + if (done) { + return; + } + done = true; + + deferred.resolve(_rejected(exception)); + }]); + }); + + // Progress propagator need to be attached in the current tick. + self.promiseDispatch(void 0, "when", [void 0, function (value) { + var newValue; + var threw = false; + try { + newValue = _progressed(value); + } catch (e) { + threw = true; + if (Q.onerror) { + Q.onerror(e); + } else { + throw e; + } + } + + if (!threw) { + deferred.notify(newValue); + } + }]); + + return deferred.promise; +}; + +/** + * Registers an observer on a promise. + * + * Guarantees: + * + * 1. that fulfilled and rejected will be called only once. + * 2. that either the fulfilled callback or the rejected callback will be + * called, but not both. + * 3. that fulfilled and rejected will not be called in this turn. + * + * @param value promise or immediate reference to observe + * @param fulfilled function to be called with the fulfilled value + * @param rejected function to be called with the rejection exception + * @param progressed function to be called on any progress notifications + * @return promise for the return value from the invoked callback + */ +Q.when = when; +function when(value, fulfilled, rejected, progressed) { + return Q(value).then(fulfilled, rejected, progressed); +} + +Promise.prototype.thenResolve = function (value) { + return this.then(function () { return value; }); +}; + +Q.thenResolve = function (promise, value) { + return Q(promise).thenResolve(value); +}; + +Promise.prototype.thenReject = function (reason) { + return this.then(function () { throw reason; }); +}; + +Q.thenReject = function (promise, reason) { + return Q(promise).thenReject(reason); +}; + +/** + * If an object is not a promise, it is as "near" as possible. + * If a promise is rejected, it is as "near" as possible too. + * If it’s a fulfilled promise, the fulfillment value is nearer. + * If it’s a deferred promise and the deferred has been resolved, the + * resolution is "nearer". + * @param object + * @returns most resolved (nearest) form of the object + */ + +// XXX should we re-do this? +Q.nearer = nearer; +function nearer(value) { + if (isPromise(value)) { + var inspected = value.inspect(); + if (inspected.state === "fulfilled") { + return inspected.value; + } + } + return value; +} + +/** + * @returns whether the given object is a promise. + * Otherwise it is a fulfilled value. + */ +Q.isPromise = isPromise; +function isPromise(object) { + return isObject(object) && + typeof object.promiseDispatch === "function" && + typeof object.inspect === "function"; +} + +Q.isPromiseAlike = isPromiseAlike; +function isPromiseAlike(object) { + return isObject(object) && typeof object.then === "function"; +} + +/** + * @returns whether the given object is a pending promise, meaning not + * fulfilled or rejected. + */ +Q.isPending = isPending; +function isPending(object) { + return isPromise(object) && object.inspect().state === "pending"; +} + +Promise.prototype.isPending = function () { + return this.inspect().state === "pending"; +}; + +/** + * @returns whether the given object is a value or fulfilled + * promise. + */ +Q.isFulfilled = isFulfilled; +function isFulfilled(object) { + return !isPromise(object) || object.inspect().state === "fulfilled"; +} + +Promise.prototype.isFulfilled = function () { + return this.inspect().state === "fulfilled"; +}; + +/** + * @returns whether the given object is a rejected promise. + */ +Q.isRejected = isRejected; +function isRejected(object) { + return isPromise(object) && object.inspect().state === "rejected"; +} + +Promise.prototype.isRejected = function () { + return this.inspect().state === "rejected"; +}; + +//// BEGIN UNHANDLED REJECTION TRACKING + +// This promise library consumes exceptions thrown in handlers so they can be +// handled by a subsequent promise. The exceptions get added to this array when +// they are created, and removed when they are handled. Note that in ES6 or +// shimmed environments, this would naturally be a `Set`. +var unhandledReasons = []; +var unhandledRejections = []; +var trackUnhandledRejections = true; + +function resetUnhandledRejections() { + unhandledReasons.length = 0; + unhandledRejections.length = 0; + + if (!trackUnhandledRejections) { + trackUnhandledRejections = true; + } +} + +function trackRejection(promise, reason) { + if (!trackUnhandledRejections) { + return; + } + + unhandledRejections.push(promise); + if (reason && typeof reason.stack !== "undefined") { + unhandledReasons.push(reason.stack); + } else { + unhandledReasons.push("(no stack) " + reason); + } +} + +function untrackRejection(promise) { + if (!trackUnhandledRejections) { + return; + } + + var at = array_indexOf(unhandledRejections, promise); + if (at !== -1) { + unhandledRejections.splice(at, 1); + unhandledReasons.splice(at, 1); + } +} + +Q.resetUnhandledRejections = resetUnhandledRejections; + +Q.getUnhandledReasons = function () { + // Make a copy so that consumers can't interfere with our internal state. + return unhandledReasons.slice(); +}; + +Q.stopUnhandledRejectionTracking = function () { + resetUnhandledRejections(); + trackUnhandledRejections = false; +}; + +resetUnhandledRejections(); + +//// END UNHANDLED REJECTION TRACKING + +/** + * Constructs a rejected promise. + * @param reason value describing the failure + */ +Q.reject = reject; +function reject(reason) { + var rejection = Promise({ + "when": function (rejected) { + // note that the error has been handled + if (rejected) { + untrackRejection(this); + } + return rejected ? rejected(reason) : this; + } + }, function fallback() { + return this; + }, function inspect() { + return { state: "rejected", reason: reason }; + }); + + // Note that the reason has not been handled. + trackRejection(rejection, reason); + + return rejection; +} + +/** + * Constructs a fulfilled promise for an immediate reference. + * @param value immediate reference + */ +Q.fulfill = fulfill; +function fulfill(value) { + return Promise({ + "when": function () { + return value; + }, + "get": function (name) { + return value[name]; + }, + "set": function (name, rhs) { + value[name] = rhs; + }, + "delete": function (name) { + delete value[name]; + }, + "post": function (name, args) { + // Mark Miller proposes that post with no name should apply a + // promised function. + if (name === null || name === void 0) { + return value.apply(void 0, args); + } else { + return value[name].apply(value, args); + } + }, + "apply": function (thisp, args) { + return value.apply(thisp, args); + }, + "keys": function () { + return object_keys(value); + } + }, void 0, function inspect() { + return { state: "fulfilled", value: value }; + }); +} + +/** + * Converts thenables to Q promises. + * @param promise thenable promise + * @returns a Q promise + */ +function coerce(promise) { + var deferred = defer(); + nextTick(function () { + try { + promise.then(deferred.resolve, deferred.reject, deferred.notify); + } catch (exception) { + deferred.reject(exception); + } + }); + return deferred.promise; +} + +/** + * Annotates an object such that it will never be + * transferred away from this process over any promise + * communication channel. + * @param object + * @returns promise a wrapping of that object that + * additionally responds to the "isDef" message + * without a rejection. + */ +Q.master = master; +function master(object) { + return Promise({ + "isDef": function () {} + }, function fallback(op, args) { + return dispatch(object, op, args); + }, function () { + return Q(object).inspect(); + }); +} + +/** + * Spreads the values of a promised array of arguments into the + * fulfillment callback. + * @param fulfilled callback that receives variadic arguments from the + * promised array + * @param rejected callback that receives the exception if the promise + * is rejected. + * @returns a promise for the return value or thrown exception of + * either callback. + */ +Q.spread = spread; +function spread(value, fulfilled, rejected) { + return Q(value).spread(fulfilled, rejected); +} + +Promise.prototype.spread = function (fulfilled, rejected) { + return this.all().then(function (array) { + return fulfilled.apply(void 0, array); + }, rejected); +}; + +/** + * The async function is a decorator for generator functions, turning + * them into asynchronous generators. Although generators are only part + * of the newest ECMAScript 6 drafts, this code does not cause syntax + * errors in older engines. This code should continue to work and will + * in fact improve over time as the language improves. + * + * ES6 generators are currently part of V8 version 3.19 with the + * --harmony-generators runtime flag enabled. SpiderMonkey has had them + * for longer, but under an older Python-inspired form. This function + * works on both kinds of generators. + * + * Decorates a generator function such that: + * - it may yield promises + * - execution will continue when that promise is fulfilled + * - the value of the yield expression will be the fulfilled value + * - it returns a promise for the return value (when the generator + * stops iterating) + * - the decorated function returns a promise for the return value + * of the generator or the first rejected promise among those + * yielded. + * - if an error is thrown in the generator, it propagates through + * every following yield until it is caught, or until it escapes + * the generator function altogether, and is translated into a + * rejection for the promise returned by the decorated generator. + */ +Q.async = async; +function async(makeGenerator) { + return function () { + // when verb is "send", arg is a value + // when verb is "throw", arg is an exception + function continuer(verb, arg) { + var result; + + // Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only + // engine that has a deployed base of browsers that support generators. + // However, SM's generators use the Python-inspired semantics of + // outdated ES6 drafts. We would like to support ES6, but we'd also + // like to make it possible to use generators in deployed browsers, so + // we also support Python-style generators. At some point we can remove + // this block. + + if (typeof StopIteration === "undefined") { + // ES6 Generators + try { + result = generator[verb](arg); + } catch (exception) { + return reject(exception); + } + if (result.done) { + return Q(result.value); + } else { + return when(result.value, callback, errback); + } + } else { + // SpiderMonkey Generators + // FIXME: Remove this case when SM does ES6 generators. + try { + result = generator[verb](arg); + } catch (exception) { + if (isStopIteration(exception)) { + return Q(exception.value); + } else { + return reject(exception); + } + } + return when(result, callback, errback); + } + } + var generator = makeGenerator.apply(this, arguments); + var callback = continuer.bind(continuer, "next"); + var errback = continuer.bind(continuer, "throw"); + return callback(); + }; +} + +/** + * The spawn function is a small wrapper around async that immediately + * calls the generator and also ends the promise chain, so that any + * unhandled errors are thrown instead of forwarded to the error + * handler. This is useful because it's extremely common to run + * generators at the top-level to work with libraries. + */ +Q.spawn = spawn; +function spawn(makeGenerator) { + Q.done(Q.async(makeGenerator)()); +} + +// FIXME: Remove this interface once ES6 generators are in SpiderMonkey. +/** + * Throws a ReturnValue exception to stop an asynchronous generator. + * + * This interface is a stop-gap measure to support generator return + * values in older Firefox/SpiderMonkey. In browsers that support ES6 + * generators like Chromium 29, just use "return" in your generator + * functions. + * + * @param value the return value for the surrounding generator + * @throws ReturnValue exception with the value. + * @example + * // ES6 style + * Q.async(function* () { + * var foo = yield getFooPromise(); + * var bar = yield getBarPromise(); + * return foo + bar; + * }) + * // Older SpiderMonkey style + * Q.async(function () { + * var foo = yield getFooPromise(); + * var bar = yield getBarPromise(); + * Q.return(foo + bar); + * }) + */ +Q["return"] = _return; +function _return(value) { + throw new QReturnValue(value); +} + +/** + * The promised function decorator ensures that any promise arguments + * are settled and passed as values (`this` is also settled and passed + * as a value). It will also ensure that the result of a function is + * always a promise. + * + * @example + * var add = Q.promised(function (a, b) { + * return a + b; + * }); + * add(Q(a), Q(B)); + * + * @param {function} callback The function to decorate + * @returns {function} a function that has been decorated. + */ +Q.promised = promised; +function promised(callback) { + return function () { + return spread([this, all(arguments)], function (self, args) { + return callback.apply(self, args); + }); + }; +} + +/** + * sends a message to a value in a future turn + * @param object* the recipient + * @param op the name of the message operation, e.g., "when", + * @param args further arguments to be forwarded to the operation + * @returns result {Promise} a promise for the result of the operation + */ +Q.dispatch = dispatch; +function dispatch(object, op, args) { + return Q(object).dispatch(op, args); +} + +Promise.prototype.dispatch = function (op, args) { + var self = this; + var deferred = defer(); + nextTick(function () { + self.promiseDispatch(deferred.resolve, op, args); + }); + return deferred.promise; +}; + +/** + * Gets the value of a property in a future turn. + * @param object promise or immediate reference for target object + * @param name name of property to get + * @return promise for the property value + */ +Q.get = function (object, key) { + return Q(object).dispatch("get", [key]); +}; + +Promise.prototype.get = function (key) { + return this.dispatch("get", [key]); +}; + +/** + * Sets the value of a property in a future turn. + * @param object promise or immediate reference for object object + * @param name name of property to set + * @param value new value of property + * @return promise for the return value + */ +Q.set = function (object, key, value) { + return Q(object).dispatch("set", [key, value]); +}; + +Promise.prototype.set = function (key, value) { + return this.dispatch("set", [key, value]); +}; + +/** + * Deletes a property in a future turn. + * @param object promise or immediate reference for target object + * @param name name of property to delete + * @return promise for the return value + */ +Q.del = // XXX legacy +Q["delete"] = function (object, key) { + return Q(object).dispatch("delete", [key]); +}; + +Promise.prototype.del = // XXX legacy +Promise.prototype["delete"] = function (key) { + return this.dispatch("delete", [key]); +}; + +/** + * Invokes a method in a future turn. + * @param object promise or immediate reference for target object + * @param name name of method to invoke + * @param value a value to post, typically an array of + * invocation arguments for promises that + * are ultimately backed with `resolve` values, + * as opposed to those backed with URLs + * wherein the posted value can be any + * JSON serializable object. + * @return promise for the return value + */ +// bound locally because it is used by other methods +Q.mapply = // XXX As proposed by "Redsandro" +Q.post = function (object, name, args) { + return Q(object).dispatch("post", [name, args]); +}; + +Promise.prototype.mapply = // XXX As proposed by "Redsandro" +Promise.prototype.post = function (name, args) { + return this.dispatch("post", [name, args]); +}; + +/** + * Invokes a method in a future turn. + * @param object promise or immediate reference for target object + * @param name name of method to invoke + * @param ...args array of invocation arguments + * @return promise for the return value + */ +Q.send = // XXX Mark Miller's proposed parlance +Q.mcall = // XXX As proposed by "Redsandro" +Q.invoke = function (object, name /*...args*/) { + return Q(object).dispatch("post", [name, array_slice(arguments, 2)]); +}; + +Promise.prototype.send = // XXX Mark Miller's proposed parlance +Promise.prototype.mcall = // XXX As proposed by "Redsandro" +Promise.prototype.invoke = function (name /*...args*/) { + return this.dispatch("post", [name, array_slice(arguments, 1)]); +}; + +/** + * Applies the promised function in a future turn. + * @param object promise or immediate reference for target function + * @param args array of application arguments + */ +Q.fapply = function (object, args) { + return Q(object).dispatch("apply", [void 0, args]); +}; + +Promise.prototype.fapply = function (args) { + return this.dispatch("apply", [void 0, args]); +}; + +/** + * Calls the promised function in a future turn. + * @param object promise or immediate reference for target function + * @param ...args array of application arguments + */ +Q["try"] = +Q.fcall = function (object /* ...args*/) { + return Q(object).dispatch("apply", [void 0, array_slice(arguments, 1)]); +}; + +Promise.prototype.fcall = function (/*...args*/) { + return this.dispatch("apply", [void 0, array_slice(arguments)]); +}; + +/** + * Binds the promised function, transforming return values into a fulfilled + * promise and thrown errors into a rejected one. + * @param object promise or immediate reference for target function + * @param ...args array of application arguments + */ +Q.fbind = function (object /*...args*/) { + var promise = Q(object); + var args = array_slice(arguments, 1); + return function fbound() { + return promise.dispatch("apply", [ + this, + args.concat(array_slice(arguments)) + ]); + }; +}; +Promise.prototype.fbind = function (/*...args*/) { + var promise = this; + var args = array_slice(arguments); + return function fbound() { + return promise.dispatch("apply", [ + this, + args.concat(array_slice(arguments)) + ]); + }; +}; + +/** + * Requests the names of the owned properties of a promised + * object in a future turn. + * @param object promise or immediate reference for target object + * @return promise for the keys of the eventually settled object + */ +Q.keys = function (object) { + return Q(object).dispatch("keys", []); +}; + +Promise.prototype.keys = function () { + return this.dispatch("keys", []); +}; + +/** + * Turns an array of promises into a promise for an array. If any of + * the promises gets rejected, the whole array is rejected immediately. + * @param {Array*} an array (or promise for an array) of values (or + * promises for values) + * @returns a promise for an array of the corresponding values + */ +// By Mark Miller +// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled +Q.all = all; +function all(promises) { + return when(promises, function (promises) { + var countDown = 0; + var deferred = defer(); + array_reduce(promises, function (undefined, promise, index) { + var snapshot; + if ( + isPromise(promise) && + (snapshot = promise.inspect()).state === "fulfilled" + ) { + promises[index] = snapshot.value; + } else { + ++countDown; + when( + promise, + function (value) { + promises[index] = value; + if (--countDown === 0) { + deferred.resolve(promises); + } + }, + deferred.reject, + function (progress) { + deferred.notify({ index: index, value: progress }); + } + ); + } + }, void 0); + if (countDown === 0) { + deferred.resolve(promises); + } + return deferred.promise; + }); +} + +Promise.prototype.all = function () { + return all(this); +}; + +/** + * Waits for all promises to be settled, either fulfilled or + * rejected. This is distinct from `all` since that would stop + * waiting at the first rejection. The promise returned by + * `allResolved` will never be rejected. + * @param promises a promise for an array (or an array) of promises + * (or values) + * @return a promise for an array of promises + */ +Q.allResolved = deprecate(allResolved, "allResolved", "allSettled"); +function allResolved(promises) { + return when(promises, function (promises) { + promises = array_map(promises, Q); + return when(all(array_map(promises, function (promise) { + return when(promise, noop, noop); + })), function () { + return promises; + }); + }); +} + +Promise.prototype.allResolved = function () { + return allResolved(this); +}; + +/** + * @see Promise#allSettled + */ +Q.allSettled = allSettled; +function allSettled(promises) { + return Q(promises).allSettled(); +} + +/** + * Turns an array of promises into a promise for an array of their states (as + * returned by `inspect`) when they have all settled. + * @param {Array[Any*]} values an array (or promise for an array) of values (or + * promises for values) + * @returns {Array[State]} an array of states for the respective values. + */ +Promise.prototype.allSettled = function () { + return this.then(function (promises) { + return all(array_map(promises, function (promise) { + promise = Q(promise); + function regardless() { + return promise.inspect(); + } + return promise.then(regardless, regardless); + })); + }); +}; + +/** + * Captures the failure of a promise, giving an oportunity to recover + * with a callback. If the given promise is fulfilled, the returned + * promise is fulfilled. + * @param {Any*} promise for something + * @param {Function} callback to fulfill the returned promise if the + * given promise is rejected + * @returns a promise for the return value of the callback + */ +Q.fail = // XXX legacy +Q["catch"] = function (object, rejected) { + return Q(object).then(void 0, rejected); +}; + +Promise.prototype.fail = // XXX legacy +Promise.prototype["catch"] = function (rejected) { + return this.then(void 0, rejected); +}; + +/** + * Attaches a listener that can respond to progress notifications from a + * promise's originating deferred. This listener receives the exact arguments + * passed to ``deferred.notify``. + * @param {Any*} promise for something + * @param {Function} callback to receive any progress notifications + * @returns the given promise, unchanged + */ +Q.progress = progress; +function progress(object, progressed) { + return Q(object).then(void 0, void 0, progressed); +} + +Promise.prototype.progress = function (progressed) { + return this.then(void 0, void 0, progressed); +}; + +/** + * Provides an opportunity to observe the settling of a promise, + * regardless of whether the promise is fulfilled or rejected. Forwards + * the resolution to the returned promise when the callback is done. + * The callback can return a promise to defer completion. + * @param {Any*} promise + * @param {Function} callback to observe the resolution of the given + * promise, takes no arguments. + * @returns a promise for the resolution of the given promise when + * ``fin`` is done. + */ +Q.fin = // XXX legacy +Q["finally"] = function (object, callback) { + return Q(object)["finally"](callback); +}; + +Promise.prototype.fin = // XXX legacy +Promise.prototype["finally"] = function (callback) { + callback = Q(callback); + return this.then(function (value) { + return callback.fcall().then(function () { + return value; + }); + }, function (reason) { + // TODO attempt to recycle the rejection with "this". + return callback.fcall().then(function () { + throw reason; + }); + }); +}; + +/** + * Terminates a chain of promises, forcing rejections to be + * thrown as exceptions. + * @param {Any*} promise at the end of a chain of promises + * @returns nothing + */ +Q.done = function (object, fulfilled, rejected, progress) { + return Q(object).done(fulfilled, rejected, progress); +}; + +Promise.prototype.done = function (fulfilled, rejected, progress) { + var onUnhandledError = function (error) { + // forward to a future turn so that ``when`` + // does not catch it and turn it into a rejection. + nextTick(function () { + makeStackTraceLong(error, promise); + if (Q.onerror) { + Q.onerror(error); + } else { + throw error; + } + }); + }; + + // Avoid unnecessary `nextTick`ing via an unnecessary `when`. + var promise = fulfilled || rejected || progress ? + this.then(fulfilled, rejected, progress) : + this; + + if (typeof process === "object" && process && process.domain) { + onUnhandledError = process.domain.bind(onUnhandledError); + } + + promise.then(void 0, onUnhandledError); +}; + +/** + * Causes a promise to be rejected if it does not get fulfilled before + * some milliseconds time out. + * @param {Any*} promise + * @param {Number} milliseconds timeout + * @param {Any*} custom error message or Error object (optional) + * @returns a promise for the resolution of the given promise if it is + * fulfilled before the timeout, otherwise rejected. + */ +Q.timeout = function (object, ms, error) { + return Q(object).timeout(ms, error); +}; + +Promise.prototype.timeout = function (ms, error) { + var deferred = defer(); + var timeoutId = setTimeout(function () { + if (!error || "string" === typeof error) { + error = new Error(error || "Timed out after " + ms + " ms"); + error.code = "ETIMEDOUT"; + } + deferred.reject(error); + }, ms); + + this.then(function (value) { + clearTimeout(timeoutId); + deferred.resolve(value); + }, function (exception) { + clearTimeout(timeoutId); + deferred.reject(exception); + }, deferred.notify); + + return deferred.promise; +}; + +/** + * Returns a promise for the given value (or promised value), some + * milliseconds after it resolved. Passes rejections immediately. + * @param {Any*} promise + * @param {Number} milliseconds + * @returns a promise for the resolution of the given promise after milliseconds + * time has elapsed since the resolution of the given promise. + * If the given promise rejects, that is passed immediately. + */ +Q.delay = function (object, timeout) { + if (timeout === void 0) { + timeout = object; + object = void 0; + } + return Q(object).delay(timeout); +}; + +Promise.prototype.delay = function (timeout) { + return this.then(function (value) { + var deferred = defer(); + setTimeout(function () { + deferred.resolve(value); + }, timeout); + return deferred.promise; + }); +}; + +/** + * Passes a continuation to a Node function, which is called with the given + * arguments provided as an array, and returns a promise. + * + * Q.nfapply(FS.readFile, [__filename]) + * .then(function (content) { + * }) + * + */ +Q.nfapply = function (callback, args) { + return Q(callback).nfapply(args); +}; + +Promise.prototype.nfapply = function (args) { + var deferred = defer(); + var nodeArgs = array_slice(args); + nodeArgs.push(deferred.makeNodeResolver()); + this.fapply(nodeArgs).fail(deferred.reject); + return deferred.promise; +}; + +/** + * Passes a continuation to a Node function, which is called with the given + * arguments provided individually, and returns a promise. + * @example + * Q.nfcall(FS.readFile, __filename) + * .then(function (content) { + * }) + * + */ +Q.nfcall = function (callback /*...args*/) { + var args = array_slice(arguments, 1); + return Q(callback).nfapply(args); +}; + +Promise.prototype.nfcall = function (/*...args*/) { + var nodeArgs = array_slice(arguments); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + this.fapply(nodeArgs).fail(deferred.reject); + return deferred.promise; +}; + +/** + * Wraps a NodeJS continuation passing function and returns an equivalent + * version that returns a promise. + * @example + * Q.nfbind(FS.readFile, __filename)("utf-8") + * .then(console.log) + * .done() + */ +Q.nfbind = +Q.denodeify = function (callback /*...args*/) { + var baseArgs = array_slice(arguments, 1); + return function () { + var nodeArgs = baseArgs.concat(array_slice(arguments)); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + Q(callback).fapply(nodeArgs).fail(deferred.reject); + return deferred.promise; + }; +}; + +Promise.prototype.nfbind = +Promise.prototype.denodeify = function (/*...args*/) { + var args = array_slice(arguments); + args.unshift(this); + return Q.denodeify.apply(void 0, args); +}; + +Q.nbind = function (callback, thisp /*...args*/) { + var baseArgs = array_slice(arguments, 2); + return function () { + var nodeArgs = baseArgs.concat(array_slice(arguments)); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + function bound() { + return callback.apply(thisp, arguments); + } + Q(bound).fapply(nodeArgs).fail(deferred.reject); + return deferred.promise; + }; +}; + +Promise.prototype.nbind = function (/*thisp, ...args*/) { + var args = array_slice(arguments, 0); + args.unshift(this); + return Q.nbind.apply(void 0, args); +}; + +/** + * Calls a method of a Node-style object that accepts a Node-style + * callback with a given array of arguments, plus a provided callback. + * @param object an object that has the named method + * @param {String} name name of the method of object + * @param {Array} args arguments to pass to the method; the callback + * will be provided by Q and appended to these arguments. + * @returns a promise for the value or error + */ +Q.nmapply = // XXX As proposed by "Redsandro" +Q.npost = function (object, name, args) { + return Q(object).npost(name, args); +}; + +Promise.prototype.nmapply = // XXX As proposed by "Redsandro" +Promise.prototype.npost = function (name, args) { + var nodeArgs = array_slice(args || []); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + this.dispatch("post", [name, nodeArgs]).fail(deferred.reject); + return deferred.promise; +}; + +/** + * Calls a method of a Node-style object that accepts a Node-style + * callback, forwarding the given variadic arguments, plus a provided + * callback argument. + * @param object an object that has the named method + * @param {String} name name of the method of object + * @param ...args arguments to pass to the method; the callback will + * be provided by Q and appended to these arguments. + * @returns a promise for the value or error + */ +Q.nsend = // XXX Based on Mark Miller's proposed "send" +Q.nmcall = // XXX Based on "Redsandro's" proposal +Q.ninvoke = function (object, name /*...args*/) { + var nodeArgs = array_slice(arguments, 2); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + Q(object).dispatch("post", [name, nodeArgs]).fail(deferred.reject); + return deferred.promise; +}; + +Promise.prototype.nsend = // XXX Based on Mark Miller's proposed "send" +Promise.prototype.nmcall = // XXX Based on "Redsandro's" proposal +Promise.prototype.ninvoke = function (name /*...args*/) { + var nodeArgs = array_slice(arguments, 1); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + this.dispatch("post", [name, nodeArgs]).fail(deferred.reject); + return deferred.promise; +}; + +/** + * If a function would like to support both Node continuation-passing-style and + * promise-returning-style, it can end its internal promise chain with + * `nodeify(nodeback)`, forwarding the optional nodeback argument. If the user + * elects to use a nodeback, the result will be sent there. If they do not + * pass a nodeback, they will receive the result promise. + * @param object a result (or a promise for a result) + * @param {Function} nodeback a Node.js-style callback + * @returns either the promise or nothing + */ +Q.nodeify = nodeify; +function nodeify(object, nodeback) { + return Q(object).nodeify(nodeback); +} + +Promise.prototype.nodeify = function (nodeback) { + if (nodeback) { + this.then(function (value) { + nextTick(function () { + nodeback(null, value); + }); + }, function (error) { + nextTick(function () { + nodeback(error); + }); + }); + } else { + return this; + } +}; + +// All code before this point will be filtered from stack traces. +var qEndingLine = captureLine(); + +return Q; + +}); + diff --git a/mist/assets/ext/qml_messaging.js b/mist/assets/ext/qml_messaging.js new file mode 100644 index 000000000..8222c848d --- /dev/null +++ b/mist/assets/ext/qml_messaging.js @@ -0,0 +1,13 @@ +function HandleMessage(data) { + var message; + try { message = JSON.parse(data) } catch(e) {}; + + if(message) { + switch(message.type) { + case "coinbase": + return eth.coinBase(); + case "block": + return eth.blockByNumber(0); + } + } +} diff --git a/ethereal/assets/ext/string.js b/mist/assets/ext/string.js index 2473b5c36..2473b5c36 100644 --- a/ethereal/assets/ext/string.js +++ b/mist/assets/ext/string.js diff --git a/ethereal/assets/ext/test.html b/mist/assets/ext/test.html index 4bac7d36f..4bac7d36f 100644 --- a/ethereal/assets/ext/test.html +++ b/mist/assets/ext/test.html diff --git a/ethereal/assets/facet.png b/mist/assets/facet.png Binary files differindex 49a266e96..49a266e96 100644 --- a/ethereal/assets/facet.png +++ b/mist/assets/facet.png diff --git a/ethereal/assets/heart.png b/mist/assets/heart.png Binary files differindex 3c874ab7f..3c874ab7f 100644 --- a/ethereal/assets/heart.png +++ b/mist/assets/heart.png diff --git a/mist/assets/icecream.png b/mist/assets/icecream.png Binary files differnew file mode 100644 index 000000000..2438ca845 --- /dev/null +++ b/mist/assets/icecream.png diff --git a/ethereal/assets/muted/codemirror.css b/mist/assets/muted/codemirror.css index 098a317a2..098a317a2 100644 --- a/ethereal/assets/muted/codemirror.css +++ b/mist/assets/muted/codemirror.css diff --git a/ethereal/assets/muted/debugger.html b/mist/assets/muted/debugger.html index b7552f030..b7552f030 100644 --- a/ethereal/assets/muted/debugger.html +++ b/mist/assets/muted/debugger.html diff --git a/ethereal/assets/muted/eclipse.css b/mist/assets/muted/eclipse.css index 317218e3d..317218e3d 100644 --- a/ethereal/assets/muted/eclipse.css +++ b/mist/assets/muted/eclipse.css diff --git a/ethereal/assets/muted/index.html b/mist/assets/muted/index.html index 14949b5ac..14949b5ac 100644 --- a/ethereal/assets/muted/index.html +++ b/mist/assets/muted/index.html diff --git a/ethereal/assets/muted/lib/codemirror.js b/mist/assets/muted/lib/codemirror.js index 0ab217711..0ab217711 100644 --- a/ethereal/assets/muted/lib/codemirror.js +++ b/mist/assets/muted/lib/codemirror.js diff --git a/ethereal/assets/muted/lib/go.js b/mist/assets/muted/lib/go.js index 9f1c1c4ab..9f1c1c4ab 100644 --- a/ethereal/assets/muted/lib/go.js +++ b/mist/assets/muted/lib/go.js diff --git a/ethereal/assets/muted/lib/matchbrackets.js b/mist/assets/muted/lib/matchbrackets.js index dcdde81df..dcdde81df 100644 --- a/ethereal/assets/muted/lib/matchbrackets.js +++ b/mist/assets/muted/lib/matchbrackets.js diff --git a/ethereal/assets/muted/muted.js b/mist/assets/muted/muted.js index 72e858d7a..72e858d7a 100644 --- a/ethereal/assets/muted/muted.js +++ b/mist/assets/muted/muted.js diff --git a/ethereal/assets/net.png b/mist/assets/net.png Binary files differindex 65a20ea00..65a20ea00 100644 --- a/ethereal/assets/net.png +++ b/mist/assets/net.png diff --git a/ethereal/assets/network.png b/mist/assets/network.png Binary files differindex 0a9ffe2ec..0a9ffe2ec 100644 --- a/ethereal/assets/network.png +++ b/mist/assets/network.png diff --git a/ethereal/assets/new.png b/mist/assets/new.png Binary files differindex e80096748..e80096748 100644 --- a/ethereal/assets/new.png +++ b/mist/assets/new.png diff --git a/ethereal/assets/pick.png b/mist/assets/pick.png Binary files differindex 2f5a261c2..2f5a261c2 100644 --- a/ethereal/assets/pick.png +++ b/mist/assets/pick.png diff --git a/ethereal/assets/qml/QmlApp.qml b/mist/assets/qml/QmlApp.qml index f5c503f4c..f5c503f4c 100644 --- a/ethereal/assets/qml/QmlApp.qml +++ b/mist/assets/qml/QmlApp.qml diff --git a/ethereal/assets/qml/first_run.qml b/mist/assets/qml/first_run.qml index 0b1dac4c6..0b1dac4c6 100644 --- a/ethereal/assets/qml/first_run.qml +++ b/mist/assets/qml/first_run.qml diff --git a/ethereal/assets/qml/muted.qml b/mist/assets/qml/muted.qml index fac8267c4..fac8267c4 100644 --- a/ethereal/assets/qml/muted.qml +++ b/mist/assets/qml/muted.qml diff --git a/ethereal/assets/qml/test_app.qml b/mist/assets/qml/test_app.qml index c69587839..c69587839 100644 --- a/ethereal/assets/qml/test_app.qml +++ b/mist/assets/qml/test_app.qml diff --git a/ethereal/assets/qml/transactions.qml b/mist/assets/qml/transactions.qml index e9a035a85..e9a035a85 100644 --- a/ethereal/assets/qml/transactions.qml +++ b/mist/assets/qml/transactions.qml diff --git a/ethereal/assets/qml/views/chain.qml b/mist/assets/qml/views/chain.qml index 9eaa49db1..130ff8bb9 100644 --- a/ethereal/assets/qml/views/chain.qml +++ b/mist/assets/qml/views/chain.qml @@ -10,7 +10,6 @@ Rectangle { id: root property var title: "Network" property var iconSource: "../net.png" - property var secondary: "Hi" property var menuItem objectName: "chainView" @@ -99,20 +98,23 @@ Rectangle { function addBlock(block, initial) { - var txs = JSON.parse(block.transactions); - var amount = 0 if(initial == undefined){ initial = false } + /* + var txs = JSON.parse(block.transactions); if(txs != null){ amount = txs.length } + */ + var txs = block.transactions; + var amount = block.transactions.length; if(initial){ - blockModel.append({number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) + blockModel.append({size: block.size, number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) } else { - blockModel.insert(0, {number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) + blockModel.insert(0, {size: block.size, number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) } //root.secondary.text = "#" + block.number; @@ -137,7 +139,7 @@ Rectangle { anchors.top: parent.top anchors.left: parent.left Text { text: '<h3>Block details</h3>'; color: "#F2F2F2"} - Text { text: '<b>Block number:</b> ' + number; color: "#F2F2F2"} + Text { text: '<b>Block number:</b> ' + number + " (Size: " + size + ")"; color: "#F2F2F2"} Text { text: '<b>Hash:</b> ' + hash; color: "#F2F2F2"} Text { text: '<b>Coinbase:</b> <' + name + '> ' + coinbase; color: "#F2F2F2"} Text { text: '<b>Block found at:</b> ' + prettyTime; color: "#F2F2F2"} @@ -242,11 +244,11 @@ Rectangle { singleBlock.set(0,block) popup.height = 300 transactionModel.clear() - if(block.txs != undefined){ - for(var i = 0; i < block.txs.count; ++i) { + if(block.txs !== undefined){ + for(var i = 0; i < block.txs.length; i++) { transactionModel.insert(0, block.txs.get(i)) } - if(block.txs.get(0).data){ + if(block.txs.length > 0 && block.txs.get(0).data){ popup.showContractData(block.txs.get(0)) } } diff --git a/ethereal/assets/qml/views/history.qml b/mist/assets/qml/views/history.qml index 9eee883e3..9eee883e3 100644 --- a/ethereal/assets/qml/views/history.qml +++ b/mist/assets/qml/views/history.qml diff --git a/ethereal/assets/qml/views/info.qml b/mist/assets/qml/views/info.qml index ca6ca077e..8a1d4d84a 100644 --- a/ethereal/assets/qml/views/info.qml +++ b/mist/assets/qml/views/info.qml @@ -44,59 +44,103 @@ Rectangle { gui.setCustomIdentifier(text) } } - } - property var addressModel: ListModel { - id: addressModel + TextArea { + objectName: "statsPane" + width: parent.width + height: 200 + selectByMouse: true + readOnly: true + font.family: "Courier" + } } - TableView { - id: addressView + + RowLayout { + id: logLayout width: parent.width height: 200 - anchors.bottom: logLayout.top - TableViewColumn{ role: "name"; title: "name" } - TableViewColumn{ role: "address"; title: "address"; width: 300} - - model: addressModel - itemDelegate: Item { - Text { - anchors { - left: parent.left - right: parent.right - leftMargin: 10 - verticalCenter: parent.verticalCenter - } - color: styleData.textColor - elide: styleData.elideMode - text: styleData.value - font.pixelSize: 11 - MouseArea { - acceptedButtons: Qt.LeftButton | Qt.RightButton - propagateComposedEvents: true - anchors.fill: parent - onClicked: { - addressView.selection.clear() - addressView.selection.select(styleData.row) - - if(mouse.button == Qt.RightButton) { - contextMenu.row = styleData.row; - contextMenu.popup() + anchors.bottom: parent.bottom + + TableView { + id: addressView + width: parent.width + height: 200 + anchors { + left: parent.left + right: logLevelSlider.left + bottom: parent.bottom + top: parent.top + } + TableViewColumn{ role: "name"; title: "name" } + TableViewColumn{ role: "address"; title: "address"; width: 300} + + property var addressModel: ListModel { + id: addressModel + } + + model: addressModel + itemDelegate: Item { + Text { + anchors { + left: parent.left + right: parent.right + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + color: styleData.textColor + elide: styleData.elideMode + text: styleData.value + font.pixelSize: 11 + MouseArea { + acceptedButtons: Qt.LeftButton | Qt.RightButton + propagateComposedEvents: true + anchors.fill: parent + onClicked: { + addressView.selection.clear() + addressView.selection.select(styleData.row) + + if(mouse.button == Qt.RightButton) { + contextMenu.row = styleData.row; + contextMenu.popup() + } } } } } + Menu { + id: contextMenu + property var row; + + MenuItem { + text: "Copy" + onTriggered: { + copyToClipboard(addressModel.get(this.row).address) + } + } + } } - Menu { - id: contextMenu - property var row; + Slider { + id: logLevelSlider + value: gui.getLogLevelInt() + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom - MenuItem { - text: "Copy" - onTriggered: { - copyToClipboard(addressModel.get(this.row).address) - } + rightMargin: 5 + leftMargin: 5 + topMargin: 5 + bottomMargin: 5 + } + + orientation: Qt.Vertical + maximumValue: 5 + stepSize: 1 + + onValueChanged: { + gui.setLogLevel(value) } } } @@ -104,6 +148,8 @@ Rectangle { property var logModel: ListModel { id: logModel } + + /* RowLayout { id: logLayout width: parent.width @@ -147,6 +193,7 @@ Rectangle { } } } + */ function addDebugMessage(message){ debuggerLog.append({value: message}) diff --git a/ethereal/assets/qml/views/javascript.qml b/mist/assets/qml/views/javascript.qml index ea05c4148..ea05c4148 100644 --- a/ethereal/assets/qml/views/javascript.qml +++ b/mist/assets/qml/views/javascript.qml diff --git a/mist/assets/qml/views/jeffcoin/jeff.png b/mist/assets/qml/views/jeffcoin/jeff.png Binary files differnew file mode 100644 index 000000000..2b9c6651a --- /dev/null +++ b/mist/assets/qml/views/jeffcoin/jeff.png diff --git a/mist/assets/qml/views/jeffcoin/jeffcoin.qml b/mist/assets/qml/views/jeffcoin/jeffcoin.qml new file mode 100644 index 000000000..6a57791a7 --- /dev/null +++ b/mist/assets/qml/views/jeffcoin/jeffcoin.qml @@ -0,0 +1,184 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 + +Rectangle { + id: root + property var title: "JeffCoin" + property var iconSource: "./views/jeffcoin/jeff.png" + property var menuItem + property var filter + property var address: "fc0a9436890478bb9b1c6ed7455c2535366f4a99" + + function insertTx(message, blockNumber) { + if(!message) return; + + var from = message.from + var to = message.input.substr(24, 40) + var value = eth.fromNumber(message.input.substr(64, 64)) + + var me = eth.key().address; + if((to == me|| from == me) && message.input.length == 128) { + txModel.insert(0, {confirmations: blockNumber - message.number, from: from, to: to, value: value}) + } + } + + function setBalance() { + var jeffCoinAmount = eth.fromNumber(eth.storageAt(address, eth.key().address)) + " JΞF" + menuItem.secondaryTitle = jeffCoinAmount + + balance.text = "<b>Balance</b>: " + jeffCoinAmount; + } + + function onReady() { + setBalance() + + filter = new ethx.watch({latest: -1, to: address}) + filter.changed(function(messages) { + setBalance() + + var blockNumber = eth.block(-1).number; + for(var i = 0; i < messages.length; i++) { + insertTx(messages.get(i), blockNumber); + } + }); + + var blockNumber = eth.block(-1).number; + var messages = filter.messages() + for(var i = messages.length-1; i >= 0; i--) { + var message = messages.get(i) + + insertTx(message, blockNumber) + } + + var chainChanged = ethx.watch("chain") + chainChanged.changed(function() { + for(var i = 0; i < txModel.count; i++) { + var entry = txModel.get(i); + entry.confirmations++; + } + }); + } + + function onDestroy() { + filter.uninstall() + } + + ColumnLayout { + spacing: 10 + y: 40 + anchors.fill: parent + + Text { + id: balance + text: "<b>Balance</b>: " + eth.fromNumber(eth.storageAt(address, eth.key().address)) + " JΞF" + font.pixelSize: 24 + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 20 + } + } + + Rectangle { + id: newTxPane + color: "#ececec" + border.color: "#cccccc" + border.width: 1 + anchors { + top: balance.bottom + topMargin: 10 + left: parent.left + leftMargin: 5 + right: parent.right + rightMargin: 5 + } + height: 100 + + RowLayout { + id: amountFields + spacing: 10 + anchors { + top: parent.top + topMargin: 20 + left: parent.left + leftMargin: 20 + } + + Text { + text: "JΞF " + } + + // There's something off with the row layout where textfields won't listen to the width setting + Rectangle { + width: 50 + height: 20 + TextField { + id: txValue + width: parent.width + placeholderText: "0.00" + } + } + } + + RowLayout { + id: toFields + spacing: 10 + anchors { + top: amountFields.bottom + topMargin: 5 + left: parent.left + leftMargin: 20 + } + + Text { + text: "To" + } + + Rectangle { + width: 200 + height: 20 + TextField { + id: txTo + width: parent.width + placeholderText: "Address or name" + } + } + + Button { + text: "Send" + onClicked: { + eth.transact({from: eth.key().privateKey, to:address, gas: "9000", gasPrice: "100000000000", data: ["0x"+txTo.text, txValue.text]}) + } + } + } + } + + Rectangle { + anchors { + left: parent.left + right: parent.right + top: newTxPane.bottom + topMargin: 10 + bottom: parent.bottom + } + TableView { + id: txTableView + anchors.fill : parent + TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 } + TableViewColumn{ role: "from" ; title: "From" ; width: 280 } + TableViewColumn{ role: "to" ; title: "To" ; width: 280 } + TableViewColumn{ role: "confirmations" ; title: "Confirmations" ; width: 100 } + + model: ListModel { + id: txModel + Component.onCompleted: { + } + } + } + } + } +} diff --git a/ethereal/assets/qml/views/pending_tx.qml b/mist/assets/qml/views/pending_tx.qml index abfa25790..abfa25790 100644 --- a/ethereal/assets/qml/views/pending_tx.qml +++ b/mist/assets/qml/views/pending_tx.qml diff --git a/ethereal/assets/qml/views/transaction.qml b/mist/assets/qml/views/transaction.qml index fb8ba8a6d..7d689733f 100644 --- a/ethereal/assets/qml/views/transaction.qml +++ b/mist/assets/qml/views/transaction.qml @@ -180,6 +180,8 @@ Rectangle { txResult.text = "Your transaction has been submitted:\n" txOutput.text = res[0].address mainContractColumn.state = "DONE" + + console.log(res) } } } diff --git a/ethereal/assets/qml/views/wallet.qml b/mist/assets/qml/views/wallet.qml index 5e10a7022..fbe1dfd0e 100644 --- a/ethereal/assets/qml/views/wallet.qml +++ b/mist/assets/qml/views/wallet.qml @@ -9,7 +9,7 @@ import Ethereum 1.0 Rectangle { id: root property var title: "Wallet" - property var iconSource: "../wallet.png" + property var iconSource: "../facet.png" property var menuItem objectName: "walletView" @@ -151,10 +151,16 @@ Rectangle { model: ListModel { id: txModel Component.onCompleted: { - var messages = JSON.parse(eth.messages({latest: -1, from: "e6716f9544a56c530d868e4bfbacb172315bdead"})) + var filter = ethx.watch({latest: -1, from: eth.key().address}); + filter.changed(addTxs) + + addTxs(filter.messages()) + } + + function addTxs(messages) { for(var i = 0; i < messages.length; i++) { - var message = messages[i]; - this.insert(0, {num: i, from: message.from, to: message.to, value: eth.numberToHuman(message.value)}) + var message = messages.get(i); + txModel.insert(0, {num: txModel.count, from: message.from, to: message.to, value: eth.numberToHuman(message.value)}) } } } diff --git a/mist/assets/qml/wallet.qml b/mist/assets/qml/wallet.qml new file mode 100644 index 000000000..24191eae8 --- /dev/null +++ b/mist/assets/qml/wallet.qml @@ -0,0 +1,894 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +import "../ext/filter.js" as Eth +import "../ext/http.js" as Http + +ApplicationWindow { + id: root + + property alias miningButtonText: miningButton.text + property var ethx : Eth.ethx + property var web + + width: 1200 + height: 820 + minimumHeight: 300 + + title: "Mist" + + // This signal is used by the filter API. The filter API connects using this signal handler from + // the different QML files and plugins. + signal messages(var messages, int id); + function invokeFilterCallback(data, receiverSeed) { + //var messages = JSON.parse(data) + // Signal handler + messages(data, receiverSeed); + root.web.messages(data, receiverSeed); + } + + TextField { + id: copyElementHax + visible: false + } + + function copyToClipboard(text) { + copyElementHax.text = text + copyElementHax.selectAll() + copyElementHax.copy() + } + + // Takes care of loading all default plugins + Component.onCompleted: { + var wallet = addPlugin("./views/wallet.qml", {noAdd: true, close: false, section: "ethereum", active: true}); + var browser = addPlugin("./webapp.qml", {noAdd: true, close: false, section: "ethereum", active: true}); + root.web = browser.view; + + addPlugin("./views/transaction.qml", {noAdd: true, close: false, section: "legacy"}); + addPlugin("./views/chain.qml", {noAdd: true, close: false, section: "legacy"}); + addPlugin("./views/info.qml", {noAdd: true, close: false, section: "legacy"}); + addPlugin("./views/pending_tx.qml", {noAdd: true, close: false, section: "legacy"}); + addPlugin("./views/javascript.qml", {noAdd: true, close: false, section: "legacy"}); + + addPlugin("./views/jeffcoin/jeffcoin.qml", {noAdd: true, close: false, section: "apps"}) + + mainSplit.setView(wallet.view, wallet.menuItem); + + // Call the ready handler + gui.done(); + } + + function addViews(view, path, options) { + var views = mainSplit.addComponent(view, options) + views.menuItem.path = path + + mainSplit.views.push(views); + + if(!options.noAdd) { + gui.addPlugin(path) + } + + return views + } + + function addPlugin(path, options) { + try { + if(typeof(path) === "string" && /^https?/.test(path)) { + console.log('load http') + Http.request(path, function(o) { + if(o.status === 200) { + var view = Qt.createQmlObject(o.responseText, mainView, path) + addViews(view, path, options) + } + }) + + return + } + + var component = Qt.createComponent(path); + if(component.status != Component.Ready) { + if(component.status == Component.Error) { + ethx.note("error: ", component.errorString()); + } + + return + } + + var view = mainView.createView(component, options) + var views = addViews(view, path, options) + + return views + } catch(e) { + ethx.note(e) + } + } + + menuBar: MenuBar { + Menu { + title: "File" + MenuItem { + text: "Import App" + shortcut: "Ctrl+o" + onTriggered: { + generalFileDialog.show(true, importApp) + } + } + + /* + MenuItem { + text: "Browser" + onTriggered: eth.openBrowser() + } + */ + + MenuItem { + text: "Add plugin" + onTriggered: { + generalFileDialog.show(true, function(path) { + addPlugin(path, {close: true, section: "apps"}) + }) + } + } + + MenuSeparator {} + + MenuItem { + text: "Import key" + shortcut: "Ctrl+i" + onTriggered: { + generalFileDialog.show(true, function(path) { + gui.importKey(path) + }) + } + } + + MenuItem { + text: "Export keys" + shortcut: "Ctrl+e" + onTriggered: { + generalFileDialog.show(false, function(path) { + }) + } + } + + } + + Menu { + title: "Developer" + MenuItem { + iconSource: "../icecream.png" + text: "Debugger" + shortcut: "Ctrl+d" + onTriggered: eth.startDebugger() + } + + MenuItem { + text: "Import Tx" + onTriggered: { + txImportDialog.visible = true + } + } + + MenuItem { + text: "Run JS file" + onTriggered: { + generalFileDialog.show(true, function(path) { + eth.evalJavascriptFile(path) + }) + } + } + + MenuItem { + text: "Dump state" + onTriggered: { + generalFileDialog.show(false, function(path) { + // Empty hash for latest + gui.dumpState("", path) + }) + } + } + + MenuSeparator {} + + MenuItem { + id: miningSpeed + text: "Mining: Turbo" + onTriggered: { + gui.toggleTurboMining() + if(text == "Mining: Turbo") { + text = "Mining: Normal"; + } else { + text = "Mining: Turbo"; + } + } + } + } + + Menu { + title: "Network" + MenuItem { + text: "Add Peer" + shortcut: "Ctrl+p" + onTriggered: { + addPeerWin.visible = true + } + } + MenuItem { + text: "Show Peers" + shortcut: "Ctrl+e" + onTriggered: { + peerWindow.visible = true + } + } + } + + Menu { + title: "Help" + MenuItem { + text: "About" + onTriggered: { + aboutWin.visible = true + } + } + } + + Menu { + title: "GLOBAL SHORTCUTS" + visible: false + MenuItem { + visible: false + shortcut: "Ctrl+l" + onTriggered: { + url.focus = true + } + } + } + } + + statusBar: StatusBar { + height: 32 + RowLayout { + Button { + id: miningButton + text: "Start Mining" + onClicked: { + gui.toggleMining() + } + } + + RowLayout { + Label { + id: walletValueLabel + + font.pixelSize: 10 + styleColor: "#797979" + } + } + } + + Label { + y: 6 + objectName: "miningLabel" + visible: true + font.pixelSize: 10 + anchors.right: lastBlockLabel.left + anchors.rightMargin: 5 + } + + Label { + y: 6 + id: lastBlockLabel + objectName: "lastBlockLabel" + visible: true + text: "" + font.pixelSize: 10 + anchors.right: peerGroup.left + anchors.rightMargin: 5 + } + + ProgressBar { + id: syncProgressIndicator + visible: false + objectName: "syncProgressIndicator" + y: 3 + width: 140 + indeterminate: true + anchors.right: peerGroup.left + anchors.rightMargin: 5 + } + + RowLayout { + id: peerGroup + y: 7 + anchors.right: parent.right + MouseArea { + onDoubleClicked: peerWindow.visible = true + anchors.fill: parent + } + + Label { + id: peerLabel + font.pixelSize: 8 + text: "0 / 0" + } + Image { + id: peerImage + width: 10; height: 10 + source: "../network.png" + } + } + } + + + property var blockModel: ListModel { + id: blockModel + } + + SplitView { + property var views: []; + + id: mainSplit + anchors.fill: parent + resizing: false + + function setView(view, menu) { + for(var i = 0; i < views.length; i++) { + views[i].view.visible = false + views[i].menuItem.setSelection(false) + } + view.visible = true + + //menu.border.color = "#CCCCCC" + //menu.color = "#FFFFFFFF" + menu.setSelection(true) + } + + function addComponent(view, options) { + view.visible = false + view.anchors.fill = mainView + + if( !view.hasOwnProperty("iconSource") ) { + console.log("Could not load plugin. Property 'iconSourc' not found on view."); + return; + } + + var menuItem = menu.createMenuItem(view.iconSource, view, options); + if( view.hasOwnProperty("menuItem") ) { + view.menuItem = menuItem; + } + + if( view.hasOwnProperty("onReady") ) { + view.onReady.call(view) + } + + if( options.active ) { + setView(view, menuItem) + } + + + return {view: view, menuItem: menuItem} + } + + /********************* + * Main menu. + ********************/ + Rectangle { + id: menu + Layout.minimumWidth: 210 + Layout.maximumWidth: 210 + anchors.top: parent.top + color: "#ececec" + + Component { + id: menuItemTemplate + Rectangle { + id: menuItem + property var view; + property var path; + property var closable; + + property alias title: label.text + property alias icon: icon.source + property alias secondaryTitle: secondary.text + function setSelection(on) { + sel.visible = on + } + + width: 206 + height: 28 + color: "#00000000" + + anchors { + left: parent.left + leftMargin: 4 + } + + Rectangle { + id: sel + visible: false + anchors.fill: parent + color: "#00000000" + Rectangle { + id: r + anchors.fill: parent + border.color: "#CCCCCC" + border.width: 1 + radius: 5 + color: "#FFFFFFFF" + } + Rectangle { + anchors { + top: r.top + bottom: r.bottom + right: r.right + } + width: 10 + color: "#FFFFFFFF" + + Rectangle { + anchors { + left: parent.left + right: parent.right + top: parent.top + } + height: 1 + color: "#CCCCCC" + } + + Rectangle { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 1 + color: "#CCCCCC" + } + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + mainSplit.setView(view, menuItem) + } + } + + Image { + id: icon + height: 20 + width: 20 + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + leftMargin: 3 + } + MouseArea { + anchors.fill: parent + onClicked: { + menuItem.closeApp() + } + } + } + + Text { + id: label + anchors { + left: icon.right + verticalCenter: parent.verticalCenter + leftMargin: 3 + } + + color: "#0D0A01" + font.pixelSize: 12 + } + + Text { + id: secondary + anchors { + right: parent.right + rightMargin: 8 + verticalCenter: parent.verticalCenter + } + color: "#AEADBE" + font.pixelSize: 12 + } + + + function closeApp() { + if(!this.closable) { return; } + + if(this.view.hasOwnProperty("onDestroy")) { + this.view.onDestroy.call(this.view) + } + + this.view.destroy() + this.destroy() + gui.removePlugin(this.path) + } + } + } + + function createMenuItem(icon, view, options) { + if(options === undefined) { + options = {}; + } + + var section; + switch(options.section) { + case "ethereum": + section = menuDefault; + break; + case "legacy": + section = menuLegacy; + break; + default: + section = menuApps; + break; + } + + var comp = menuItemTemplate.createObject(section) + + comp.view = view + comp.title = view.title + comp.icon = view.iconSource + comp.closable = options.close; + + return comp + } + + ColumnLayout { + id: menuColumn + y: 10 + width: parent.width + anchors.left: parent.left + anchors.right: parent.right + spacing: 3 + + Text { + text: "ETHEREUM" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } + + ColumnLayout { + id: menuDefault + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + + + Text { + text: "APPS" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } + + ColumnLayout { + id: menuApps + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + + Text { + text: "DEBUG" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } + + ColumnLayout { + id: menuLegacy + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + } + } + + /********************* + * Main view + ********************/ + Rectangle { + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + color: "#00000000" + + Rectangle { + id: urlPane + height: 40 + color: "#00000000" + anchors { + left: parent.left + right: parent.right + leftMargin: 5 + rightMargin: 5 + top: parent.top + topMargin: 5 + } + TextField { + id: url + objectName: "url" + placeholderText: "DApp URL" + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: 5 + rightMargin: 5 + leftMargin: 5 + } + + Keys.onReturnPressed: { + addPlugin(this.text, {close: true, section: "apps"}) + } + } + + } + + // Border + Rectangle { + id: divider + anchors { + left: parent.left + right: parent.right + top: urlPane.bottom + } + z: -1 + height: 1 + color: "#CCCCCC" + } + + Rectangle { + id: mainView + color: "#00000000" + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.top: divider.bottom + + function createView(component) { + var view = component.createObject(mainView) + + return view; + } + } + } + } + + + /****************** + * Dialogs + *****************/ + FileDialog { + id: generalFileDialog + property var callback; + onAccepted: { + var path = this.fileUrl.toString(); + callback.call(this, path); + } + + function show(selectExisting, callback) { + generalFileDialog.callback = callback; + generalFileDialog.selectExisting = selectExisting; + + this.open(); + } + } + + + /****************** + * Wallet functions + *****************/ + function importApp(path) { + var ext = path.split('.').pop() + if(ext == "html" || ext == "htm") { + eth.openHtml(path) + }else if(ext == "qml"){ + addPlugin(path, {close: true, section: "apps"}) + } + } + + + function setWalletValue(value) { + walletValueLabel.text = value + } + + function loadPlugin(name) { + console.log("Loading plugin" + name) + var view = mainView.addPlugin(name) + } + + function setPeers(text) { + peerLabel.text = text + } + + function addPeer(peer) { + // We could just append the whole peer object but it cries if you try to alter them + peerModel.append({ip: peer.ip, port: peer.port, lastResponse:timeAgo(peer.lastSend), latency: peer.latency, version: peer.version}) + } + + function resetPeers(){ + peerModel.clear() + } + + function timeAgo(unixTs){ + var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000 + return (lapsed + " seconds ago") + } + + function convertToPretty(unixTs){ + var a = new Date(unixTs*1000); + var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; + var year = a.getFullYear(); + var month = months[a.getMonth()]; + var date = a.getDate(); + var hour = a.getHours(); + var min = a.getMinutes(); + var sec = a.getSeconds(); + var time = date+' '+month+' '+year+' '+hour+':'+min+':'+sec ; + return time; + } + + /********************** + * Windows + *********************/ + Window { + id: peerWindow + //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint + height: 200 + width: 700 + Rectangle { + anchors.fill: parent + property var peerModel: ListModel { + id: peerModel + } + TableView { + anchors.fill: parent + id: peerTable + model: peerModel + TableViewColumn{width: 100; role: "ip" ; title: "IP" } + TableViewColumn{width: 60; role: "port" ; title: "Port" } + TableViewColumn{width: 140; role: "lastResponse"; title: "Last event" } + TableViewColumn{width: 100; role: "latency"; title: "Latency" } + TableViewColumn{width: 260; role: "version" ; title: "Version" } + } + } + } + + Window { + id: aboutWin + visible: false + title: "About" + minimumWidth: 350 + maximumWidth: 350 + maximumHeight: 200 + minimumHeight: 200 + + Image { + id: aboutIcon + height: 150 + width: 150 + fillMode: Image.PreserveAspectFit + smooth: true + source: "../facet.png" + x: 10 + y: 10 + } + + Text { + anchors.left: aboutIcon.right + anchors.leftMargin: 10 + anchors.top: parent.top + anchors.topMargin: 30 + font.pointSize: 12 + text: "<h2>Mist (0.6.5)</h2><h4>Amalthea</h4><br><h3>Development</h3>Jeffrey Wilcke<br>Viktor Trón<br><h3>Building</h3>Maran Hidskes" + } + } + + Window { + id: txImportDialog + minimumWidth: 270 + maximumWidth: 270 + maximumHeight: 50 + minimumHeight: 50 + TextField { + id: txImportField + width: 170 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + onAccepted: { + } + } + Button { + anchors.left: txImportField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Import" + onClicked: { + eth.importTx(txImportField.text) + txImportField.visible = false + } + } + Component.onCompleted: { + addrField.focus = true + } + } + + Window { + id: addPeerWin + visible: false + minimumWidth: 300 + maximumWidth: 300 + maximumHeight: 50 + minimumHeight: 50 + title: "Connect to peer" + + ComboBox { + id: addrField + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: addPeerButton.left + anchors.leftMargin: 10 + anchors.rightMargin: 10 + onAccepted: { + eth.connectToPeer(addrField.currentText) + addPeerWin.visible = false + } + + editable: true + model: ListModel { id: pastPeers } + + Component.onCompleted: { + var ips = eth.pastPeers() + for(var i = 0; i < ips.length; i++) { + pastPeers.append({text: ips.get(i)}) + } + + pastPeers.insert(0, {text: "poc-6.ethdev.com:30303"}) + } + } + + Button { + id: addPeerButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 10 + text: "Add" + onClicked: { + eth.connectToPeer(addrField.currentText) + addPeerWin.visible = false + } + } + Component.onCompleted: { + addrField.focus = true + } + } + } diff --git a/mist/assets/qml/webapp.qml b/mist/assets/qml/webapp.qml new file mode 100644 index 000000000..8801b6b47 --- /dev/null +++ b/mist/assets/qml/webapp.qml @@ -0,0 +1,409 @@ +import QtQuick 2.0 +import QtWebKit 3.0 +import QtWebKit.experimental 1.0 +import QtQuick.Controls 1.0; +import QtQuick.Controls.Styles 1.0 +import QtQuick.Layouts 1.0; +import QtQuick.Window 2.1; +import Ethereum 1.0 + +import "../ext/qml_messaging.js" as Messaging + +//ApplicationWindow { + Rectangle { + id: window + property var title: "Browser" + property var iconSource: "../browser.png" + property var menuItem + + property alias url: webview.url + property alias webView: webview + + Component.onCompleted: { + webview.url = "http://etherian.io" + } + + signal messages(var messages, int id); + onMessages: { + // Bit of a cheat to get proper JSON + var m = JSON.parse(JSON.parse(JSON.stringify(messages))) + webview.postEvent("messages", [m, id]); + } + + Item { + objectName: "root" + id: root + anchors.fill: parent + state: "inspectorShown" + + RowLayout { + id: navBar + height: 40 + anchors { + left: parent.left + right: parent.right + leftMargin: 7 + } + + Button { + id: back + onClicked: { + webview.goBack() + } + style: ButtonStyle { + background: Image { + source: "../back.png" + width: 30 + height: 30 + } + } + } + + TextField { + anchors { + left: back.right + right: toggleInspector.left + leftMargin: 5 + rightMargin: 5 + } + text: "http://etherian.io" + id: uriNav + y: parent.height / 2 - this.height / 2 + + Keys.onReturnPressed: { + webview.url = this.text; + } + } + + Button { + id: toggleInspector + anchors { + right: parent.right + } + iconSource: "../bug.png" + onClicked: { + if(inspector.visible == true){ + inspector.visible = false + }else{ + inspector.visible = true + inspector.url = webview.experimental.remoteInspectorUrl + } + } + } + } + + + WebView { + objectName: "webView" + id: webview + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + top: navBar.bottom + } + + property var cleanPath: false + onNavigationRequested: { + if(!this.cleanPath) { + var uri = request.url.toString(); + if(!/.*\:\/\/.*/.test(uri)) { + uri = "http://" + uri; + } + + var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/ + + if(reg.test(uri)) { + uri.replace(reg, function(match, pre, domain, path) { + uri = pre; + + var lookup = eth.lookupDomain(domain.substring(0, domain.length - 4)); + var ip = []; + for(var i = 0, l = lookup.length; i < l; i++) { + ip.push(lookup.charCodeAt(i)) + } + + if(ip.length != 0) { + uri += lookup; + } else { + uri += domain; + } + + uri += path; + }); + } + + this.cleanPath = true; + + webview.url = uri; + } else { + // Prevent inf loop. + this.cleanPath = false; + } + } + + function sendMessage(data) { + webview.experimental.postMessage(JSON.stringify(data)) + } + + onTitleChanged: { + var data = Messaging.HandleMessage(title); + if(data) { + sendMessage(data) + } + } + + experimental.preferences.javascriptEnabled: true + experimental.preferences.navigatorQtObjectEnabled: true + experimental.preferences.developerExtrasEnabled: true + experimental.userScripts: ["../ext/q.js", "../ext/pre.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"] + experimental.onMessageReceived: { + console.log("[onMessageReceived]: ", message.data) + // TODO move to messaging.js + var data = JSON.parse(message.data) + + try { + switch(data.call) { + case "compile": + postData(data._seed, eth.compile(data.args[0])) + break + + case "getCoinBase": + postData(data._seed, eth.coinBase()) + + break + + case "getIsListening": + postData(data._seed, eth.isListening()) + + break + + case "getIsMining": + postData(data._seed, eth.isMining()) + + break + + case "getPeerCount": + postData(data._seed, eth.peerCount()) + + break + + case "getCountAt": + require(1) + postData(data._seed, eth.txCountAt(data.args[0])) + + break + + case "getCodeAt": + require(1) + var code = eth.codeAt(data.args[0]) + postData(data._seed, code); + + break + + case "getBlockByNumber": + var block = eth.blockByNumber(data.args[0]) + postData(data._seed, block) + + break + + case "getBlockByHash": + var block = eth.blockByHash(data.args[0]) + postData(data._seed, block) + + break + + case "transact": + require(5) + + var tx = eth.transact(data.args) + postData(data._seed, tx) + + break + + case "getStorageAt": + require(2); + + var storage = eth.storageAt(data.args[0], data.args[1]); + postData(data._seed, storage) + + break + + case "call": + require(1); + var ret = eth.call(data.args) + postData(data._seed, ret) + + break + + case "getEachStorage": + require(1); + var storage = JSON.parse(eth.eachStorage(data.args[0])) + postData(data._seed, storage) + + break + + case "getTransactionsFor": + require(1); + var txs = eth.transactionsFor(data.args[0], true) + postData(data._seed, txs) + + break + + case "getBalanceAt": + require(1); + + postData(data._seed, eth.balanceAt(data.args[0])); + + break + + case "getKey": + var key = eth.key().privateKey; + + postData(data._seed, key) + break + + case "watch": + require(2) + eth.watch(data.args[0], data.args[1]) + + case "disconnect": + require(1) + postData(data._seed, null) + + break; + + case "getSecretToAddress": + require(1) + + var addr = eth.secretToAddress(data.args[0]) + console.log("getsecret", addr) + postData(data._seed, addr) + + break; + + case "messages": + require(1); + + var messages = JSON.parse(eth.getMessages(data.args[0])) + postData(data._seed, messages) + + break + + case "mutan": + require(1) + + var code = eth.compileMutan(data.args[0]) + postData(data._seed, "0x"+code) + + break; + + case "newFilterString": + require(1) + var id = eth.newFilterString(data.args[0]) + postData(data._seed, id); + break; + case "newFilter": + require(1) + var id = eth.newFilter(data.args[0]) + + postData(data._seed, id); + break; + + case "getMessages": + require(1); + + var messages = eth.messages(data.args[0]); + var m = JSON.parse(JSON.parse(JSON.stringify(messages))) + postData(data._seed, m); + + break; + + case "deleteFilter": + require(1); + eth.uninstallFilter(data.args[0]) + break; + } + } catch(e) { + console.log(data.call + ": " + e) + + postData(data._seed, null); + } + } + + + function post(seed, data) { + postData(data._seed, data) + } + + function require(args, num) { + if(args.length < num) { + throw("required argument count of "+num+" got "+args.length); + } + } + function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) + } + function postEvent(event, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) + } + + function onWatchedCb(data, id) { + var messages = JSON.parse(data) + postEvent("watched:"+id, messages) + } + + function onNewBlockCb(block) { + postEvent("block:new", block) + } + function onObjectChangeCb(stateObject) { + postEvent("object:"+stateObject.address(), stateObject) + } + function onStorageChangeCb(storageObject) { + var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":"); + postEvent(ev, [storageObject.address, storageObject.value]) + } + } + + + Rectangle { + id: sizeGrip + color: "gray" + visible: false + height: 10 + anchors { + left: root.left + right: root.right + } + y: Math.round(root.height * 2 / 3) + + MouseArea { + anchors.fill: parent + drag.target: sizeGrip + drag.minimumY: 0 + drag.maximumY: root.height + drag.axis: Drag.YAxis + } + } + + WebView { + id: inspector + visible: false + anchors { + left: root.left + right: root.right + top: sizeGrip.bottom + bottom: root.bottom + } + } + + states: [ + State { + name: "inspectorShown" + PropertyChanges { + target: inspector + } + } + ] + } + } diff --git a/ethereal/assets/tx.png b/mist/assets/tx.png Binary files differindex 62204c315..62204c315 100644 --- a/ethereal/assets/tx.png +++ b/mist/assets/tx.png diff --git a/ethereal/assets/util/test.html b/mist/assets/util/test.html index d458e6670..d458e6670 100644 --- a/ethereal/assets/util/test.html +++ b/mist/assets/util/test.html diff --git a/ethereal/assets/wallet.png b/mist/assets/wallet.png Binary files differindex 92c401e52..92c401e52 100644 --- a/ethereal/assets/wallet.png +++ b/mist/assets/wallet.png diff --git a/mist/bindings.go b/mist/bindings.go new file mode 100644 index 000000000..141c4a469 --- /dev/null +++ b/mist/bindings.go @@ -0,0 +1,148 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strconv" + + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethlog" + "github.com/ethereum/eth-go/ethpipe" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" +) + +type plugin struct { + Name string `json:"name"` + Path string `json:"path"` +} + +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") + + view := gui.getObjectByName("infoView") + for _, line := range lines { + view.Call("addLog", line) + } + */ +} +func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (*ethpipe.JSReceipt, error) { + var data string + if len(recipient) == 0 { + code, err := ethutil.Compile(d, false) + if err != nil { + return nil, err + } + data = ethutil.Bytes2Hex(code) + } else { + data = ethutil.Bytes2Hex(utils.FormatTransactionData(d)) + } + + return gui.pipe.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data) +} + +func (gui *Gui) SetCustomIdentifier(customIdentifier string) { + gui.clientIdentity.SetCustomIdentifier(customIdentifier) + gui.config.Save("id", customIdentifier) +} + +func (gui *Gui) GetCustomIdentifier() string { + return gui.clientIdentity.GetCustomIdentifier() +} + +func (gui *Gui) ToggleTurboMining() { + gui.miner.ToggleTurbo() +} + +// functions that allow Gui to implement interface ethlog.LogSystem +func (gui *Gui) SetLogLevel(level ethlog.LogLevel) { + gui.logLevel = level + gui.stdLog.SetLogLevel(level) + gui.config.Save("loglevel", level) +} + +func (gui *Gui) GetLogLevel() ethlog.LogLevel { + return gui.logLevel +} + +func (self *Gui) AddPlugin(pluginPath string) { + self.plugins[pluginPath] = plugin{Name: pluginPath, Path: pluginPath} + + json, _ := json.MarshalIndent(self.plugins, "", " ") + ethutil.WriteFile(ethutil.Config.ExecPath+"/plugins.json", json) +} + +func (self *Gui) RemovePlugin(pluginPath string) { + delete(self.plugins, pluginPath) + + json, _ := json.MarshalIndent(self.plugins, "", " ") + ethutil.WriteFile(ethutil.Config.ExecPath+"/plugins.json", json) +} + +// 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 (self *Gui) DumpState(hash, path string) { + var stateDump []byte + + if len(hash) == 0 { + stateDump = self.eth.StateManager().CurrentState().Dump() + } else { + var block *ethchain.Block + if hash[0] == '#' { + i, _ := strconv.Atoi(hash[1:]) + block = self.eth.BlockChain().GetBlockByNumber(uint64(i)) + } else { + block = self.eth.BlockChain().GetBlock(ethutil.Hex2Bytes(hash)) + } + + if block == nil { + logger.Infof("block err: not found %s\n", hash) + return + } + + stateDump = block.State().Dump() + } + + file, err := os.OpenFile(path[7:], os.O_CREATE|os.O_RDWR, os.ModePerm) + if err != nil { + logger.Infoln("dump err: ", err) + return + } + defer file.Close() + + logger.Infof("dumped state (%s) to %s\n", hash, path) + + file.Write(stateDump) +} +func (gui *Gui) ToggleMining() { + var txt string + if gui.eth.Mining { + utils.StopMining(gui.eth) + txt = "Start mining" + + gui.getObjectByName("miningLabel").Set("visible", false) + } else { + utils.StartMining(gui.eth) + gui.miner = utils.GetMiner() + txt = "Stop mining" + + gui.getObjectByName("miningLabel").Set("visible", true) + } + + gui.win.Root().Set("miningButtonText", txt) +} diff --git a/ethereal/debugger.go b/mist/debugger.go index 7bc544377..9d1de8c42 100644 --- a/ethereal/debugger.go +++ b/mist/debugger.go @@ -5,6 +5,7 @@ import ( "math/big" "strconv" "strings" + "unicode" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethstate" @@ -93,9 +94,7 @@ func (self *DebuggerWindow) ClearLog() { } func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, dataStr string) { - if !self.Db.done { - self.Db.Q <- true - } + self.Stop() defer func() { if r := recover(); r != nil { @@ -185,6 +184,12 @@ func (self *DebuggerWindow) Continue() { self.Next() } +func (self *DebuggerWindow) Stop() { + if !self.Db.done { + self.Db.Q <- true + } +} + func (self *DebuggerWindow) ExecCommand(command string) { if len(command) > 0 { cmd := strings.Split(command, " ") @@ -271,9 +276,20 @@ func (d *Debugger) halting(pc int, op ethvm.OpCode, mem *ethvm.Memory, stack *et 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 i := 0; i+16 <= mem.Len(); i += 16 { + dat := mem.Data()[i : i+16] + var str string + + for _, d := range dat { + if unicode.IsGraphic(rune(d)) { + str += string(d) + } else { + str += "?" + } + } + + d.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("%s % x", str, dat)}) + addr += 16 } for _, val := range stack.Data() { @@ -284,6 +300,12 @@ func (d *Debugger) halting(pc int, op ethvm.OpCode, mem *ethvm.Memory, stack *et d.win.Root().Call("setStorage", storeVal{fmt.Sprintf("% x", key), fmt.Sprintf("% x", node.Str())}) }) + stackFrameAt := new(big.Int).SetBytes(mem.Get(0, 32)) + psize := mem.Len() - int(new(big.Int).SetBytes(mem.Get(0, 32)).Uint64()) + d.win.Root().ObjectByName("stackFrame").Set("text", fmt.Sprintf(`<b>stack ptr</b>: %v`, stackFrameAt)) + d.win.Root().ObjectByName("stackSize").Set("text", fmt.Sprintf(`<b>stack size</b>: %d`, psize)) + d.win.Root().ObjectByName("memSize").Set("text", fmt.Sprintf(`<b>mem size</b>: %v`, mem.Len())) + out: for { select { diff --git a/mist/errors.go b/mist/errors.go new file mode 100644 index 000000000..409b7a281 --- /dev/null +++ b/mist/errors.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "os" + + "gopkg.in/qml.v1" +) + +func ErrorWindow(err error) { + engine := qml.NewEngine() + component, e := engine.LoadString("local", qmlErr) + if e != nil { + fmt.Println("err:", err) + os.Exit(1) + } + + win := component.CreateWindow(nil) + win.Root().ObjectByName("label").Set("text", err.Error()) + win.Show() + win.Wait() +} + +const qmlErr = ` +import QtQuick 2.0; import QtQuick.Controls 1.0; +ApplicationWindow { + width: 600; height: 150; + flags: Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint + title: "Error" + Text { + x: parent.width / 2 - this.width / 2; + y: parent.height / 2 - this.height / 2; + objectName: "label"; + } +} +` diff --git a/ethereal/ext_app.go b/mist/ext_app.go index 514084c97..514084c97 100644 --- a/ethereal/ext_app.go +++ b/mist/ext_app.go diff --git a/ethereal/flags.go b/mist/flags.go index c9327c3d3..d2e7d3fb0 100644 --- a/ethereal/flags.go +++ b/mist/flags.go @@ -1,15 +1,16 @@ package main import ( - "bitbucket.org/kardianos/osext" "flag" "fmt" - "github.com/ethereum/eth-go/ethlog" "os" "os/user" "path" "path/filepath" "runtime" + + "bitbucket.org/kardianos/osext" + "github.com/ethereum/eth-go/ethlog" ) var Identifier string @@ -43,7 +44,7 @@ func defaultAssetPath() string { // 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") { + if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "mist") { assetPath = path.Join(pwd, "assets") } else { switch runtime.GOOS { @@ -52,7 +53,7 @@ func defaultAssetPath() string { exedir, _ := osext.ExecutableFolder() assetPath = filepath.Join(exedir, "../Resources") case "linux": - assetPath = "/usr/share/ethereal" + assetPath = "/usr/share/mist" case "windows": assetPath = "./assets" default: @@ -63,7 +64,7 @@ func defaultAssetPath() string { } func defaultDataDir() string { usr, _ := user.Current() - return path.Join(usr.HomeDir, ".ethereal") + return path.Join(usr.HomeDir, ".mist") } var defaultConfigFile = path.Join(defaultDataDir(), "conf.ini") @@ -78,7 +79,7 @@ func Init() { flag.StringVar(&KeyRing, "keyring", "", "identifier for keyring to use") flag.StringVar(&KeyStore, "keystore", "db", "system to store keyrings: db|file (db)") flag.StringVar(&OutboundPort, "port", "30303", "listening port") - flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") + flag.BoolVar(&UseUPnP, "upnp", true, "enable UPnP support") flag.IntVar(&MaxPeer, "maxpeer", 10, "maximum desired peers") flag.IntVar(&RpcPort, "rpcport", 8080, "port to start json-rpc server on") flag.BoolVar(&StartRpc, "rpc", false, "start rpc server") diff --git a/ethereal/gui.go b/mist/gui.go index 6d16ec484..f80e46761 100644 --- a/ethereal/gui.go +++ b/mist/gui.go @@ -1,11 +1,14 @@ package main +import "C" + import ( "bytes" "encoding/json" "fmt" "math/big" - "os" + "path" + "runtime" "strconv" "strings" "time" @@ -19,16 +22,33 @@ import ( "github.com/ethereum/eth-go/ethreact" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethwire" - "github.com/ethereum/go-ethereum/utils" "gopkg.in/qml.v1" ) -var logger = ethlog.NewLogger("GUI") +/* +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 + } -type plugin struct { - Name string `json:"name"` - Path string `json:"path"` + return ptr.Interface().(uintptr), nil } +*/ + +var logger = ethlog.NewLogger("GUI") type Gui struct { // The main application window @@ -56,7 +76,8 @@ type Gui struct { plugins map[string]plugin - miner *ethminer.Miner + miner *ethminer.Miner + stdLog ethlog.LogSystem } // Create GUI, but doesn't start it @@ -68,12 +89,7 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, clientIden 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, err := ethutil.ReadAllFile(ethutil.Config.ExecPath + "/plugins.json") - if err != nil { - fmt.Println(err) - } - fmt.Println("plugins:", string(data)) - + data, _ := ethutil.ReadAllFile(path.Join(ethutil.Config.ExecPath, "plugins.json")) json.Unmarshal([]byte(data), &gui.plugins) return gui @@ -100,6 +116,16 @@ func (gui *Gui) Start(assetPath string) { 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")) @@ -145,24 +171,6 @@ func (gui *Gui) Stop() { logger.Infoln("Stopped") } -func (gui *Gui) ToggleMining() { - var txt string - if gui.eth.Mining { - utils.StopMining(gui.eth) - txt = "Start mining" - - gui.getObjectByName("miningLabel").Set("visible", false) - } else { - utils.StartMining(gui.eth) - gui.miner = utils.GetMiner() - txt = "Stop mining" - - gui.getObjectByName("miningLabel").Set("visible", true) - } - - 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 { @@ -176,44 +184,9 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { return gui.win, nil } -func (self *Gui) DumpState(hash, path string) { - var stateDump []byte - - if len(hash) == 0 { - stateDump = self.eth.StateManager().CurrentState().Dump() - } else { - var block *ethchain.Block - if hash[0] == '#' { - i, _ := strconv.Atoi(hash[1:]) - block = self.eth.BlockChain().GetBlockByNumber(uint64(i)) - } else { - block = self.eth.BlockChain().GetBlock(ethutil.Hex2Bytes(hash)) - } - - if block == nil { - logger.Infof("block err: not found %s\n", hash) - return - } - - stateDump = block.State().Dump() - } - - file, err := os.OpenFile(path[7:], os.O_CREATE|os.O_RDWR, os.ModePerm) - if err != nil { - logger.Infoln("dump err: ", err) - return - } - defer file.Close() - - logger.Infof("dumped state (%s) to %s\n", hash, path) - - file.Write(stateDump) -} - // 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) { @@ -337,7 +310,7 @@ func (gui *Gui) insertTransaction(window string, tx *ethchain.Transaction) { ptx.Address = r if window == "post" { - gui.getObjectByName("transactionView").Call("addTx", ptx, inout) + //gui.getObjectByName("transactionView").Call("addTx", ptx, inout) } else { gui.getObjectByName("pendingTxView").Call("addTx", ptx, inout) } @@ -397,6 +370,8 @@ func (gui *Gui) update() { }() for _, plugin := range gui.plugins { + logger.Infoln("Loading plugin ", plugin.Name) + gui.win.Root().Call("addPlugin", plugin.Path, "") } @@ -411,6 +386,7 @@ func (gui *Gui) update() { peerUpdateTicker := time.NewTicker(5 * time.Second) generalUpdateTicker := time.NewTicker(1 * time.Second) + statsUpdateTicker := time.NewTicker(5 * time.Second) state := gui.eth.StateManager().TransState() @@ -450,12 +426,12 @@ func (gui *Gui) update() { if bytes.Compare(tx.Sender(), gui.address()) == 0 { object.SubAmount(tx.Value) - gui.getObjectByName("transactionView").Call("addTx", ethpipe.NewJSTx(tx), "send") + //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.getObjectByName("transactionView").Call("addTx", ethpipe.NewJSTx(tx), "recv") gui.txDb.Put(tx.Hash(), tx.RlpEncode()) } @@ -487,6 +463,10 @@ func (gui *Gui) update() { pow := gui.miner.GetPow() miningLabel.Set("text", "Mining @ "+strconv.FormatInt(pow.GetHashrate(), 10)+"Khash") } + + case <-statsUpdateTicker.C: + gui.setStatsPane() + } } }() @@ -506,9 +486,30 @@ func (gui *Gui) update() { reactor.Subscribe("peerList", peerChan) } -func (gui *Gui) CopyToClipboard(data string) { - //clipboard.WriteAll("test") - fmt.Println("COPY currently BUGGED. Here are the contents:\n", data) +func (gui *Gui) setStatsPane() { + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + + statsPane := gui.getObjectByName("statsPane") + statsPane.Set("text", fmt.Sprintf(`###### Mist 0.6.5 (%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() { @@ -527,82 +528,3 @@ func (gui *Gui) privateKey() string { func (gui *Gui) address() []byte { return gui.eth.KeyManager().Address() } - -func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (*ethpipe.JSReceipt, error) { - var data string - if len(recipient) == 0 { - code, err := ethutil.Compile(d, false) - if err != nil { - return nil, err - } - data = ethutil.Bytes2Hex(code) - } else { - data = ethutil.Bytes2Hex(utils.FormatTransactionData(d)) - } - - return gui.pipe.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data) -} - -func (gui *Gui) SetCustomIdentifier(customIdentifier string) { - gui.clientIdentity.SetCustomIdentifier(customIdentifier) - gui.config.Save("id", customIdentifier) -} - -func (gui *Gui) GetCustomIdentifier() string { - return gui.clientIdentity.GetCustomIdentifier() -} - -func (gui *Gui) ToggleTurboMining() { - gui.miner.ToggleTurbo() -} - -// functions that allow Gui to implement interface ethlog.LogSystem -func (gui *Gui) SetLogLevel(level ethlog.LogLevel) { - gui.logLevel = level - gui.config.Save("loglevel", level) -} - -func (gui *Gui) GetLogLevel() ethlog.LogLevel { - return gui.logLevel -} - -func (self *Gui) AddPlugin(pluginPath string) { - self.plugins[pluginPath] = plugin{Name: "SomeName", Path: pluginPath} - - json, _ := json.MarshalIndent(self.plugins, "", " ") - ethutil.WriteFile(ethutil.Config.ExecPath+"/plugins.json", json) -} - -func (self *Gui) RemovePlugin(pluginPath string) { - delete(self.plugins, pluginPath) - - json, _ := json.MarshalIndent(self.plugins, "", " ") - ethutil.WriteFile(ethutil.Config.ExecPath+"/plugins.json", json) -} - -// 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") - - view := gui.getObjectByName("infoView") - for _, line := range lines { - view.Call("addLog", line) - } - */ -} diff --git a/ethereal/html_container.go b/mist/html_container.go index 69edea570..69edea570 100644 --- a/ethereal/html_container.go +++ b/mist/html_container.go diff --git a/ethereal/main.go b/mist/main.go index 4cb8630e8..747616f8f 100644 --- a/ethereal/main.go +++ b/mist/main.go @@ -11,8 +11,8 @@ import ( ) const ( - ClientIdentifier = "Ethereal" - Version = "0.6.4" + ClientIdentifier = "Mist" + Version = "0.6.5" ) var ethereum *eth.Ethereum @@ -25,9 +25,15 @@ func run() error { utils.InitDataDir(Datadir) - utils.InitLogging(Datadir, LogFile, LogLevel, DebugFile) + stdLog := utils.InitLogging(Datadir, LogFile, LogLevel, DebugFile) db := utils.NewDatabase() + err := utils.DBSanityCheck(db) + if err != nil { + ErrorWindow(err) + + os.Exit(1) + } keyManager := utils.NewKeyManager(KeyStore, Datadir, db) @@ -47,6 +53,7 @@ func run() error { } gui := NewWindow(ethereum, config, clientIdentity, KeyRing, LogLevel) + gui.stdLog = stdLog utils.RegisterInterrupt(func(os.Signal) { gui.Stop() @@ -64,7 +71,6 @@ func main() { // This is a bit of a cheat, but ey! os.Setenv("QTWEBKIT_INSPECTOR_SERVER", "127.0.0.1:99999") - //qml.Init(nil) qml.Run(run) var interrupted = false diff --git a/ethereal/qml_container.go b/mist/qml_container.go index 85bd7c699..85bd7c699 100644 --- a/ethereal/qml_container.go +++ b/mist/qml_container.go diff --git a/ethereal/ui_lib.go b/mist/ui_lib.go index 4b8210da6..a913af7db 100644 --- a/ethereal/ui_lib.go +++ b/mist/ui_lib.go @@ -37,11 +37,14 @@ type UiLib struct { jsEngine *javascript.JSRE filterCallbacks map[int][]int - filters map[int]*GuiFilter } func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { - return &UiLib{JSPipe: ethpipe.NewJSPipe(eth), engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(eth), filterCallbacks: make(map[int][]int), filters: make(map[int]*GuiFilter)} + return &UiLib{JSPipe: ethpipe.NewJSPipe(eth), engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(eth), filterCallbacks: make(map[int][]int)} //, filters: make(map[int]*ethpipe.JSFilter)} +} + +func (self *UiLib) Notef(args []interface{}) { + logger.Infoln(args...) } func (self *UiLib) LookupDomain(domain string) string { @@ -53,7 +56,7 @@ func (self *UiLib) LookupDomain(domain string) string { data := world.Config().Get("DnsReg").StorageString(domain).Bytes() // Left padded = A record, Right padded = CNAME - if data[0] == 0 { + if len(data) > 0 && data[0] == 0 { data = bytes.TrimLeft(data, "\x00") var ipSlice []string for _, d := range data { @@ -68,6 +71,10 @@ func (self *UiLib) LookupDomain(domain string) string { } } +func (self *UiLib) PastPeers() *ethutil.List { + return ethutil.NewList(eth.PastPeers()) +} + func (self *UiLib) ImportTx(rlpTx string) { tx := ethchain.NewTransactionFromBytes(ethutil.Hex2Bytes(rlpTx)) self.eth.TxPool().QueueTransaction(tx) @@ -160,49 +167,40 @@ func (self *UiLib) StartDebugger() { dbWindow.Show() } -func (self *UiLib) RegisterFilter(object map[string]interface{}, seed int) { - filter := &GuiFilter{ethpipe.NewJSFilterFromMap(object, self.eth), seed} - self.filters[seed] = filter - +func (self *UiLib) NewFilter(object map[string]interface{}) int { + filter, id := self.eth.InstallFilter(object) filter.MessageCallback = func(messages ethstate.Messages) { - for _, callbackSeed := range self.filterCallbacks[seed] { - self.win.Root().Call("invokeFilterCallback", filter.MessagesToJson(messages), seed, callbackSeed) - } + self.win.Root().Call("invokeFilterCallback", ethpipe.ToJSMessages(messages), id) } + return id } -func (self *UiLib) RegisterFilterString(typ string, seed int) { - filter := &GuiFilter{ethpipe.NewJSFilterFromMap(nil, self.eth), seed} - self.filters[seed] = filter - - if typ == "chain" { - filter.BlockCallback = func(block *ethchain.Block) { - for _, callbackSeed := range self.filterCallbacks[seed] { - self.win.Root().Call("invokeFilterCallback", "{}", seed, callbackSeed) - } - } +func (self *UiLib) NewFilterString(typ string) int { + filter, id := self.eth.InstallFilter(nil) + filter.BlockCallback = func(block *ethchain.Block) { + self.win.Root().Call("invokeFilterCallback", "{}", id) } -} -func (self *UiLib) RegisterFilterCallback(seed, cbSeed int) { - self.filterCallbacks[seed] = append(self.filterCallbacks[seed], cbSeed) + return id } -func (self *UiLib) UninstallFilter(seed int) { - filter := self.filters[seed] +func (self *UiLib) Messages(id int) *ethutil.List { + filter := self.eth.GetFilter(id) if filter != nil { - filter.Uninstall() - delete(self.filters, seed) + messages := filter.Find() + + return ethpipe.ToJSMessages(messages) } + + return ethutil.EmptyList() } -type GuiFilter struct { - *ethpipe.JSFilter - seed int +func (self *UiLib) UninstallFilter(id int) { + self.eth.UninstallFilter(id) } -func (self *UiLib) Transact(object map[string]interface{}) (*ethpipe.JSReceipt, error) { +func mapToTxParams(object map[string]interface{}) map[string]string { // Default values if object["from"] == nil { object["from"] = "" @@ -224,6 +222,8 @@ func (self *UiLib) Transact(object map[string]interface{}) (*ethpipe.JSReceipt, var data []string if list, ok := object["data"].(*qml.List); ok { list.Convert(&data) + } else if str, ok := object["data"].(string); ok { + data = []string{str} } for _, str := range data { @@ -239,13 +239,49 @@ func (self *UiLib) Transact(object map[string]interface{}) (*ethpipe.JSReceipt, dataStr += str } + object["data"] = dataStr + fmt.Println(object) + + conv := make(map[string]string) + for key, value := range object { + if v, ok := value.(string); ok { + conv[key] = v + } + } + + return conv +} + +func (self *UiLib) Transact(params map[string]interface{}) (*ethpipe.JSReceipt, error) { + object := mapToTxParams(params) return self.JSPipe.Transact( - object["from"].(string), - object["to"].(string), - object["value"].(string), - object["gas"].(string), - object["gasPrice"].(string), - dataStr, + object["from"], + object["to"], + object["value"], + object["gas"], + object["gasPrice"], + object["data"], + ) +} + +func (self *UiLib) Compile(code string) (string, error) { + bcode, err := ethutil.Compile(code, false) + if err != nil { + return err.Error(), err + } + + return ethutil.Bytes2Hex(bcode), err +} + +func (self *UiLib) Call(params map[string]interface{}) (string, error) { + object := mapToTxParams(params) + + return self.JSPipe.Execute( + object["to"], + object["value"], + object["gas"], + object["gasPrice"], + object["data"], ) } diff --git a/utils/cmd.go b/utils/cmd.go index cda735c27..700542cae 100644 --- a/utils/cmd.go +++ b/utils/cmd.go @@ -80,6 +80,16 @@ func confirm(message string) bool { return r == "y" } +func DBSanityCheck(db ethutil.Database) error { + d, _ := db.Get([]byte("ProtocolVersion")) + protov := ethutil.NewValue(d).Uint() + if protov != eth.ProtocolVersion && protov != 0 { + return fmt.Errorf("Database version mismatch. Protocol(%d / %d). `rm -rf %s`", protov, eth.ProtocolVersion, ethutil.Config.ExecPath+"/database") + } + + return nil +} + func InitDataDir(Datadir string) { _, err := os.Stat(Datadir) if err != nil { @@ -90,18 +100,22 @@ func InitDataDir(Datadir string) { } } -func InitLogging(Datadir string, LogFile string, LogLevel int, DebugFile string) { +func InitLogging(Datadir string, LogFile string, LogLevel int, DebugFile string) ethlog.LogSystem { var writer io.Writer if LogFile == "" { writer = os.Stdout } else { writer = openLogFile(Datadir, LogFile) } - ethlog.AddLogSystem(ethlog.NewStdLogSystem(writer, log.LstdFlags, ethlog.LogLevel(LogLevel))) + + sys := ethlog.NewStdLogSystem(writer, log.LstdFlags, ethlog.LogLevel(LogLevel)) + ethlog.AddLogSystem(sys) if DebugFile != "" { writer = openLogFile(Datadir, DebugFile) ethlog.AddLogSystem(ethlog.NewStdLogSystem(writer, log.LstdFlags, ethlog.DebugLevel)) } + + return sys } func InitConfig(ConfigFile string, Datadir string, EnvPrefix string) *ethutil.ConfigManager { @@ -112,7 +126,6 @@ func InitConfig(ConfigFile string, Datadir string, EnvPrefix string) *ethutil.Co func exit(err error) { status := 0 if err != nil { - fmt.Println(err) logger.Errorln("Fatal: ", err) status = 1 } @@ -176,7 +189,7 @@ func DefaultAssetPath() string { // 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") { + if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "mist") { assetPath = path.Join(pwd, "assets") } else { switch runtime.GOOS { @@ -185,7 +198,7 @@ func DefaultAssetPath() string { exedir, _ := osext.ExecutableFolder() assetPath = filepath.Join(exedir, "../Resources") case "linux": - assetPath = "/usr/share/ethereal" + assetPath = "/usr/share/mist" case "windows": assetPath = "./assets" default: |