Marek Kotewicz
10 years ago
54 changed files with 5547 additions and 304 deletions
@ -0,0 +1,139 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappHost.cpp
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "DappHost.h" |
|||
#include <QUrl> |
|||
#include <microhttpd.h> |
|||
#include <boost/algorithm/string.hpp> |
|||
#include <libdevcore/Common.h> |
|||
|
|||
using namespace dev; |
|||
|
|||
DappHost::DappHost(int _port, int _threads): |
|||
m_port(_port), m_threads(_threads), m_running(false), m_daemon(nullptr) |
|||
{ |
|||
startListening(); |
|||
} |
|||
|
|||
DappHost::~DappHost() |
|||
{ |
|||
stopListening(); |
|||
} |
|||
|
|||
void DappHost::startListening() |
|||
{ |
|||
if(!this->m_running) |
|||
{ |
|||
this->m_daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, this->m_port, nullptr, nullptr, &DappHost::callback, this, MHD_OPTION_THREAD_POOL_SIZE, this->m_threads, MHD_OPTION_END); |
|||
if (this->m_daemon != nullptr) |
|||
this->m_running = true; |
|||
} |
|||
} |
|||
|
|||
void DappHost::stopListening() |
|||
{ |
|||
if(this->m_running) |
|||
{ |
|||
MHD_stop_daemon(this->m_daemon); |
|||
this->m_running = false; |
|||
} |
|||
} |
|||
|
|||
void DappHost::sendOptionsResponse(MHD_Connection* _connection) |
|||
{ |
|||
MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); |
|||
MHD_add_response_header(result, "Allow", "GET, OPTIONS"); |
|||
MHD_add_response_header(result, "Access-Control-Allow-Headers", "origin, content-type, accept"); |
|||
MHD_add_response_header(result, "DAV", "1"); |
|||
MHD_queue_response(_connection, MHD_HTTP_OK, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
void DappHost::sendNotAllowedResponse(MHD_Connection* _connection) |
|||
{ |
|||
MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); |
|||
MHD_add_response_header(result, "Allow", "GET, OPTIONS"); |
|||
MHD_queue_response(_connection, MHD_HTTP_METHOD_NOT_ALLOWED, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
void DappHost::sendResponse(std::string const& _url, MHD_Connection* _connection) |
|||
{ |
|||
QUrl requestUrl(QString::fromStdString(_url)); |
|||
QString path = requestUrl.path().toLower(); |
|||
if (path.isEmpty()) |
|||
path = "/"; |
|||
|
|||
bytesConstRef response; |
|||
unsigned code = MHD_HTTP_NOT_FOUND; |
|||
std::string contentType; |
|||
|
|||
while (!path.isEmpty()) |
|||
{ |
|||
auto iter = m_entriesByPath.find(path); |
|||
if (iter != m_entriesByPath.end()) |
|||
{ |
|||
ManifestEntry const* entry = iter->second; |
|||
auto contentIter = m_dapp.content.find(entry->hash); |
|||
if (contentIter == m_dapp.content.end()) |
|||
break; |
|||
|
|||
response = bytesConstRef(contentIter->second.data(), contentIter->second.size()); |
|||
code = entry->httpStatus != 0 ? entry->httpStatus : MHD_HTTP_OK; |
|||
contentType = entry->contentType; |
|||
break; |
|||
} |
|||
path.truncate(path.length() - 1); |
|||
path = path.mid(0, path.lastIndexOf('/')); |
|||
} |
|||
|
|||
MHD_Response *result = MHD_create_response_from_data(response.size(), const_cast<byte*>(response.data()), 0, 1); |
|||
if (!contentType.empty()) |
|||
MHD_add_response_header(result, "Content-Type", contentType.c_str()); |
|||
MHD_queue_response(_connection, code, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
int DappHost::callback(void* _cls, MHD_Connection* _connection, char const* _url, char const* _method, char const* _version, char const* _uploadData, size_t* _uploadDataSize, void** _conCls) |
|||
{ |
|||
(void)_version; |
|||
(void)_uploadData; |
|||
(void)_uploadDataSize; |
|||
(void)_conCls; |
|||
DappHost* host = static_cast<DappHost*>(_cls); |
|||
if (std::string("GET") == _method) |
|||
host->sendResponse(std::string(_url), _connection); |
|||
else if (std::string("OPTIONS") == _method) |
|||
host->sendOptionsResponse(_connection); |
|||
else |
|||
host->sendNotAllowedResponse(_connection); |
|||
return MHD_YES; |
|||
} |
|||
|
|||
QUrl DappHost::hostDapp(Dapp&& _dapp) |
|||
{ |
|||
m_dapp = std::move(_dapp); |
|||
m_entriesByPath.clear(); |
|||
for (ManifestEntry const& entry: m_dapp.manifest.entries) |
|||
m_entriesByPath[QString::fromStdString(entry.path)] = &entry; |
|||
|
|||
return QUrl(QString("http://localhost:%1/").arg(m_port)); |
|||
} |
@ -0,0 +1,58 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappHost.h
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <QUrl> |
|||
#include <QString> |
|||
#include "DappLoader.h" |
|||
|
|||
struct MHD_Daemon; |
|||
struct MHD_Connection; |
|||
|
|||
/// DApp web server. Servers web content, resolves paths by hashes
|
|||
class DappHost |
|||
{ |
|||
public: |
|||
/// @param _port Network pork to listen for incoming connections
|
|||
/// @param _threads Max number of threads to process requests
|
|||
DappHost(int _port, int _threads = 10); |
|||
virtual ~DappHost(); |
|||
/// Load and host a dapp. Previsous dapp in discarded. Synchronous
|
|||
QUrl hostDapp(Dapp&& _dapp); |
|||
|
|||
private: |
|||
void startListening(); |
|||
void stopListening(); |
|||
void sendOptionsResponse(MHD_Connection* _connection); |
|||
void sendNotAllowedResponse(MHD_Connection* _connection); |
|||
void sendResponse(std::string const& _url, MHD_Connection* _connection); |
|||
static int callback(void* _cls, MHD_Connection* _connection, char const* _url, char const* _method, char const* _version, char const* _uploadData, size_t* _uploadDataSize, void** _conCls); |
|||
|
|||
int m_port; |
|||
int m_threads; |
|||
bool m_running; |
|||
MHD_Daemon* m_daemon; |
|||
Dapp m_dapp; |
|||
std::map<QString, ManifestEntry const*> m_entriesByPath; |
|||
}; |
|||
|
@ -0,0 +1,203 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappLoader.cpp
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <algorithm> |
|||
#include <json/json.h> |
|||
#include <QUrl> |
|||
#include <QStringList> |
|||
#include <QNetworkAccessManager> |
|||
#include <QNetworkReply> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/RLP.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libethcore/CommonJS.h> |
|||
#include <libethereum/Client.h> |
|||
#include <libwebthree/WebThree.h> |
|||
#include "DappLoader.h" |
|||
|
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::crypto; |
|||
|
|||
Address c_registrar = Address("0000000000000000000000000000000000000a28"); |
|||
Address c_urlHint = Address("0000000000000000000000000000000000000a29"); |
|||
|
|||
QString contentsOfQResource(std::string const& res); |
|||
|
|||
DappLoader::DappLoader(QObject* _parent, WebThreeDirect* _web3): |
|||
QObject(_parent), m_web3(_web3) |
|||
{ |
|||
connect(&m_net, &QNetworkAccessManager::finished, this, &DappLoader::downloadComplete); |
|||
} |
|||
|
|||
DappLocation DappLoader::resolveAppUri(QString const& _uri) |
|||
{ |
|||
QUrl url(_uri); |
|||
if (!url.scheme().isEmpty() && url.scheme() != "eth") |
|||
throw dev::Exception(); //TODO:
|
|||
|
|||
QStringList parts = url.host().split('.', QString::SkipEmptyParts); |
|||
QStringList domainParts; |
|||
std::reverse(parts.begin(), parts.end()); |
|||
parts.append(url.path().split('/', QString::SkipEmptyParts)); |
|||
|
|||
Address address = c_registrar; |
|||
Address lastAddress; |
|||
int partIndex = 0; |
|||
|
|||
h256 contentHash; |
|||
|
|||
while (address && partIndex < parts.length()) |
|||
{ |
|||
lastAddress = address; |
|||
string32 name = { 0 }; |
|||
QByteArray utf8 = parts[partIndex].toUtf8(); |
|||
std::copy(utf8.data(), utf8.data() + utf8.size(), name.data()); |
|||
address = abiOut<Address>(web3()->ethereum()->call(address, abiIn("addr(string32)", name))); |
|||
domainParts.append(parts[partIndex]); |
|||
if (!address) |
|||
{ |
|||
//we have the address of the last part, try to get content hash
|
|||
contentHash = abiOut<h256>(web3()->ethereum()->call(lastAddress, abiIn("content(string32)", name))); |
|||
if (!contentHash) |
|||
throw dev::Exception() << errinfo_comment("Can't resolve address"); |
|||
} |
|||
++partIndex; |
|||
} |
|||
|
|||
|
|||
string32 contentUrl = abiOut<string32>(web3()->ethereum()->call(c_urlHint, abiIn("url(hash256)", contentHash))); |
|||
QString domain = domainParts.join('/'); |
|||
parts.erase(parts.begin(), parts.begin() + partIndex); |
|||
QString path = parts.join('/'); |
|||
QString contentUrlString = QString::fromUtf8(std::string(contentUrl.data(), contentUrl.size()).c_str()); |
|||
if (!contentUrlString.startsWith("http://") || !contentUrlString.startsWith("https://")) |
|||
contentUrlString = "http://" + contentUrlString; |
|||
return DappLocation { domain, path, contentUrlString, contentHash }; |
|||
} |
|||
|
|||
void DappLoader::downloadComplete(QNetworkReply* _reply) |
|||
{ |
|||
try |
|||
{ |
|||
//try to interpret as rlp
|
|||
QByteArray data = _reply->readAll(); |
|||
_reply->deleteLater(); |
|||
|
|||
h256 expected = m_uriHashes[_reply->request().url()]; |
|||
bytes package(reinterpret_cast<unsigned char const*>(data.constData()), reinterpret_cast<unsigned char const*>(data.constData() + data.size())); |
|||
Secp256k1 dec; |
|||
dec.decrypt(expected, package); |
|||
h256 got = sha3(package); |
|||
if (got != expected) |
|||
{ |
|||
//try base64
|
|||
data = QByteArray::fromBase64(data); |
|||
package = bytes(reinterpret_cast<unsigned char const*>(data.constData()), reinterpret_cast<unsigned char const*>(data.constData() + data.size())); |
|||
dec.decrypt(expected, package); |
|||
got = sha3(package); |
|||
if (got != expected) |
|||
throw dev::Exception() << errinfo_comment("Dapp content hash does not match"); |
|||
} |
|||
|
|||
RLP rlp(package); |
|||
loadDapp(rlp); |
|||
} |
|||
catch (...) |
|||
{ |
|||
qWarning() << tr("Error downloading DApp: ") << boost::current_exception_diagnostic_information().c_str(); |
|||
emit dappError(); |
|||
} |
|||
|
|||
} |
|||
|
|||
void DappLoader::loadDapp(RLP const& _rlp) |
|||
{ |
|||
Dapp dapp; |
|||
unsigned len = _rlp.itemCountStrict(); |
|||
dapp.manifest = loadManifest(_rlp[0].toString()); |
|||
for (unsigned c = 1; c < len; ++c) |
|||
{ |
|||
bytesConstRef content = _rlp[c].toBytesConstRef(); |
|||
h256 hash = sha3(content); |
|||
auto entry = std::find_if(dapp.manifest.entries.cbegin(), dapp.manifest.entries.cend(), [=](ManifestEntry const& _e) { return _e.hash == hash; }); |
|||
if (entry != dapp.manifest.entries.cend()) |
|||
{ |
|||
if (entry->path == "/deployment.js") |
|||
{ |
|||
//inject web3 code
|
|||
QString code; |
|||
code += contentsOfQResource(":/js/bignumber.min.js"); |
|||
code += "\n"; |
|||
code += contentsOfQResource(":/js/webthree.js"); |
|||
code += "\n"; |
|||
code += contentsOfQResource(":/js/setup.js"); |
|||
code += "\n"; |
|||
QByteArray res = code.toLatin1(); |
|||
bytes b(res.data(), res.data() + res.size()); |
|||
b.insert(b.end(), content.begin(), content.end()); |
|||
dapp.content[hash] = b; |
|||
} |
|||
else |
|||
dapp.content[hash] = content.toBytes(); |
|||
} |
|||
else |
|||
throw dev::Exception() << errinfo_comment("Dapp content hash does not match"); |
|||
} |
|||
emit dappReady(dapp); |
|||
} |
|||
|
|||
Manifest DappLoader::loadManifest(std::string const& _manifest) |
|||
{ |
|||
/// https://github.com/ethereum/go-ethereum/wiki/URL-Scheme
|
|||
Manifest manifest; |
|||
Json::Reader jsonReader; |
|||
Json::Value root; |
|||
jsonReader.parse(_manifest, root, false); |
|||
|
|||
Json::Value entries = root["entries"]; |
|||
for (Json::ValueIterator it = entries.begin(); it != entries.end(); ++it) |
|||
{ |
|||
Json::Value const& entryValue = *it; |
|||
std::string path = entryValue["path"].asString(); |
|||
if (path.size() == 0 || path[0] != '/') |
|||
path = "/" + path; |
|||
std::string contentType = entryValue["contentType"].asString(); |
|||
std::string strHash = entryValue["hash"].asString(); |
|||
if (strHash.length() == 64) |
|||
strHash = "0x" + strHash; |
|||
h256 hash = jsToFixed<32>(strHash); |
|||
unsigned httpStatus = entryValue["status"].asInt(); |
|||
manifest.entries.push_back(ManifestEntry{ path, hash, contentType, httpStatus }); |
|||
} |
|||
return manifest; |
|||
} |
|||
|
|||
void DappLoader::loadDapp(QString const& _uri) |
|||
{ |
|||
DappLocation location = resolveAppUri(_uri); |
|||
QUrl uri(location.contentUri); |
|||
QNetworkRequest request(uri); |
|||
m_uriHashes[uri] = location.contentHash; |
|||
m_net.get(request); |
|||
} |
|||
|
@ -0,0 +1,94 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappLoader.h
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <string> |
|||
#include <vector> |
|||
#include <QObject> |
|||
#include <QString> |
|||
#include <QUrl> |
|||
#include <QNetworkAccessManager> |
|||
#include <libdevcore/FixedHash.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
class WebThreeDirect; |
|||
class RLP; |
|||
} |
|||
|
|||
struct ManifestEntry |
|||
{ |
|||
std::string path; |
|||
dev::h256 hash; |
|||
std::string contentType; |
|||
unsigned httpStatus; |
|||
}; |
|||
|
|||
struct Manifest |
|||
{ |
|||
std::vector<ManifestEntry> entries; |
|||
}; |
|||
|
|||
struct Dapp |
|||
{ |
|||
Manifest manifest; |
|||
std::map<dev::h256, dev::bytes> content; |
|||
}; |
|||
|
|||
|
|||
struct DappLocation |
|||
{ |
|||
QString canonDomain; |
|||
QString path; |
|||
QString contentUri; |
|||
dev::h256 contentHash; |
|||
}; |
|||
|
|||
///Downloads, unpacks and prepares DApps for hosting
|
|||
class DappLoader: public QObject |
|||
{ |
|||
Q_OBJECT |
|||
public: |
|||
DappLoader(QObject* _parent, dev::WebThreeDirect* _web3); |
|||
///Load a new DApp. Resolves a name with a name reg contract. Asynchronous. dappReady is emitted once everything is read, dappError othervise
|
|||
///@param _uri Eth name path
|
|||
void loadDapp(QString const& _uri); |
|||
|
|||
signals: |
|||
void dappReady(Dapp& _dapp); |
|||
void dappError(); |
|||
|
|||
private slots: |
|||
void downloadComplete(QNetworkReply* _reply); |
|||
|
|||
private: |
|||
dev::WebThreeDirect* web3() const { return m_web3; } |
|||
DappLocation resolveAppUri(QString const& _uri); |
|||
void loadDapp(dev::RLP const& _rlp); |
|||
Manifest loadManifest(std::string const& _manifest); |
|||
|
|||
dev::WebThreeDirect* m_web3; |
|||
QNetworkAccessManager m_net; |
|||
std::map<QUrl, dev::h256> m_uriHashes; |
|||
}; |
|||
|
@ -0,0 +1,28 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file WebPage.cpp
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "WebPage.h" |
|||
|
|||
void WebPage::javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel _level, const QString& _message, int _lineNumber, const QString& _sourceID) |
|||
{ |
|||
QString prefix = _level == QWebEnginePage::ErrorMessageLevel ? "error" : _level == QWebEnginePage::WarningMessageLevel ? "warning" : ""; |
|||
emit consoleMessage(QString("%1(%2:%3):%4").arg(prefix).arg(_sourceID).arg(_lineNumber).arg(_message)); |
|||
} |
@ -0,0 +1,40 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file WebPage.h
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <QString> |
|||
#pragma GCC diagnostic push |
|||
#pragma GCC diagnostic ignored "-Wpedantic" //QtWebEngineWidgets/qwebenginecertificateerror.h:78:348: error: extra ';'
|
|||
#include <QtWebEngineWidgets/QWebEnginePage> |
|||
#pragma GCC diagnostic pop |
|||
|
|||
class WebPage: public QWebEnginePage |
|||
{ |
|||
Q_OBJECT |
|||
public: |
|||
WebPage(QObject* _parent): QWebEnginePage(_parent) { } |
|||
signals: |
|||
void consoleMessage(QString const& _msg); |
|||
|
|||
protected: |
|||
void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel _level, const QString& _message, int _lineNumber, const QString& _sourceID) override; |
|||
}; |
@ -1,102 +0,0 @@ |
|||
|
|||
/** |
|||
* This plugin exposes 'evaluateExpression' method which should be used |
|||
* to evaluate natspec description |
|||
*/ |
|||
|
|||
/// Object which should be used by NatspecExpressionEvaluator
|
|||
/// abi - abi of the contract that will be used
|
|||
/// method - name of the method that is called
|
|||
/// params - input params of the method that will be called
|
|||
var globals = { |
|||
abi: [], |
|||
method: "", |
|||
params: [] |
|||
}; |
|||
|
|||
/// Helper method
|
|||
/// Should be called to copy values from object to global context
|
|||
var copyToContext = function (obj, context) { |
|||
var keys = Object.keys(obj); |
|||
keys.forEach(function (key) { |
|||
context[key] = obj[key]; |
|||
}); |
|||
} |
|||
|
|||
/// Helper method
|
|||
/// Should be called to get method with given name from the abi
|
|||
/// @param contract's abi
|
|||
/// @param name of the method that we are looking for
|
|||
var getMethodWithName = function(abi, name) { |
|||
for (var i = 0; i < abi.length; i++) { |
|||
if (abi[i].name === name) { |
|||
return abi[i]; |
|||
} |
|||
} |
|||
//console.warn('could not find method with name: ' + name);
|
|||
return undefined; |
|||
}; |
|||
|
|||
/// Function called to get all contract's storage values
|
|||
/// @returns hashmap with contract properties which are used
|
|||
/// TODO: check if this function will be used
|
|||
var getContractProperties = function (address, abi) { |
|||
return {}; |
|||
}; |
|||
|
|||
/// Function called to get all contract's methods
|
|||
/// @returns hashmap with used contract's methods
|
|||
/// TODO: check if this function will be used
|
|||
var getContractMethods = function (address, abi) { |
|||
//return web3.eth.contract(address, abi); // commented out web3 usage
|
|||
return {}; |
|||
}; |
|||
|
|||
/// Function called to get all contract method input variables
|
|||
/// @returns hashmap with all contract's method input variables
|
|||
var getMethodInputParams = function (method, params) { |
|||
return method.inputs.reduce(function (acc, current, index) { |
|||
acc[current.name] = params[index]; |
|||
return acc; |
|||
}, {}); |
|||
}; |
|||
|
|||
/// Should be called to evaluate single expression
|
|||
/// Is internally using javascript's 'eval' method
|
|||
/// Should be checked if it is safe
|
|||
var evaluateExpression = function (expression) { |
|||
|
|||
var self = this; |
|||
|
|||
//var storage = getContractProperties(address, abi);
|
|||
//var methods = getContractMethods(address, abi);
|
|||
|
|||
var method = getMethodWithName(globals.abi, globals.method); |
|||
if (method) { |
|||
var input = getMethodInputParams(method, globals.params); |
|||
copyToContext(input, self); |
|||
} |
|||
|
|||
// TODO: test if it is safe
|
|||
var evaluatedExpression = ""; |
|||
|
|||
// match everything in `` quotes
|
|||
var pattern = /\`(?:\\.|[^`\\])*\`/gim |
|||
var match; |
|||
var lastIndex = 0; |
|||
while ((match = pattern.exec(expression)) !== null) { |
|||
var startIndex = pattern.lastIndex - match[0].length; |
|||
|
|||
var toEval = match[0].slice(1, match[0].length - 1); |
|||
|
|||
evaluatedExpression += expression.slice(lastIndex, startIndex); |
|||
evaluatedExpression += eval(toEval).toString(); |
|||
|
|||
lastIndex = pattern.lastIndex; |
|||
} |
|||
|
|||
evaluatedExpression += expression.slice(lastIndex); |
|||
|
|||
return evaluatedExpression; |
|||
}; |
|||
|
@ -1,5 +1,5 @@ |
|||
<RCC> |
|||
<qresource prefix="/natspec"> |
|||
<file>natspec.js</file> |
|||
<file alias="natspec.js">natspecjs/dist/natspec.min.js</file> |
|||
</qresource> |
|||
</RCC> |
|||
|
@ -0,0 +1,3 @@ |
|||
# VIM stuff |
|||
*.swp |
|||
node_modules/ |
@ -0,0 +1,8 @@ |
|||
language: node_js |
|||
node_js: |
|||
- "0.11" |
|||
- "0.10" |
|||
before_script: |
|||
- npm install |
|||
after_script: |
|||
- npm run-script test-coveralls |
@ -0,0 +1,47 @@ |
|||
# natspec.js |
|||
Javascript Library used to evaluate natspec expressions |
|||
|
|||
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] |
|||
|
|||
[travis-image]: https://travis-ci.org/ethereum/natspec.js.svg |
|||
[travis-url]: https://travis-ci.org/ethereum/natspec.js |
|||
[coveralls-image]: https://coveralls.io/repos/ethereum/natspec.js/badge.svg?branch=master |
|||
[coveralls-url]: https://coveralls.io/r/ethereum/natspec.js?branch=master |
|||
|
|||
## Usage |
|||
|
|||
It exposes global object `natspec` with method `evaluateExpression`. |
|||
|
|||
```javascript |
|||
var natspec = require('natspec'); |
|||
|
|||
var natspecExpression = "Will multiply `a` by 7 and return `a * 7`."; |
|||
var call = { |
|||
method: 'multiply', |
|||
abi: abi, |
|||
transaction: transaction |
|||
}; |
|||
|
|||
var evaluatedExpression = natspec.evaluateExpression(natspecExpression, call); |
|||
console.log(evaluatedExpression); // "Will multiply 4 by 7 and return 28." |
|||
``` |
|||
|
|||
More examples are available [here](https://github.com/ethereum/natspec.js/blob/master/test/test.js). |
|||
|
|||
## Building |
|||
|
|||
```bash |
|||
npm run-script build |
|||
``` |
|||
|
|||
## Testing (mocha) |
|||
|
|||
```bash |
|||
npm test |
|||
``` |
|||
|
|||
## Wiki |
|||
|
|||
* [Ethereum Natural Specification Format](https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format) |
|||
* [Natspec Example](https://github.com/ethereum/wiki/wiki/Natspec-Example) |
|||
|
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,13 @@ |
|||
<!doctype> |
|||
<html> |
|||
|
|||
<head> |
|||
<script type="text/javascript" src="../dist/natspec.js"></script> |
|||
<script type="text/javascript"> |
|||
var natspec = require('natspec'); |
|||
</script> |
|||
</head> |
|||
<body> |
|||
</body> |
|||
</html> |
|||
|
@ -0,0 +1,186 @@ |
|||
/* |
|||
This file is part of natspec.js. |
|||
|
|||
natspec.js is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU Lesser General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
natspec.js is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public License |
|||
along with natspec.js. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file natspec.js |
|||
* @authors: |
|||
* Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
var abi = require('./node_modules/ethereum.js/lib/abi.js'); |
|||
|
|||
/** |
|||
* This object should be used to evaluate natspec expression |
|||
* It has one method evaluateExpression which shoul be used |
|||
*/ |
|||
var natspec = (function () { |
|||
/** |
|||
* Helper method |
|||
* Should be called to copy values from object to global context |
|||
* |
|||
* @method copyToContext |
|||
* @param {Object} object from which we want to copy properties |
|||
* @param {Object} object to which we copy |
|||
*/ |
|||
var copyToContext = function (obj, context) { |
|||
Object.keys(obj).forEach(function (key) { |
|||
context[key] = obj[key]; |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Should be used to generate codes, which will be evaluated |
|||
* |
|||
* @method generateCode |
|||
* @param {Object} object from which code will be generated |
|||
* @return {String} javascript code which is used to initalized variables |
|||
*/ |
|||
var generateCode = function (obj) { |
|||
return Object.keys(obj).reduce(function (acc, key) { |
|||
return acc + "var " + key + " = context['" + key + "'];\n"; |
|||
}, ""); |
|||
}; |
|||
|
|||
/** |
|||
* Helper method |
|||
* Should be called to get method with given name from the abi |
|||
* |
|||
* @method getMethodWithName |
|||
* @param {Array} contract's abi |
|||
* @param {String} name of the method that we are looking for |
|||
* @return {Object} abi for method with name |
|||
*/ |
|||
var getMethodWithName = function(abi, name) { |
|||
return abi.filter(function (method) { |
|||
return method.name === name; |
|||
})[0]; |
|||
}; |
|||
|
|||
/** |
|||
* Should be used to get all contract method input variables |
|||
* |
|||
* @method getMethodInputParams |
|||
* @param {Object} abi for certain method |
|||
* @param {Object} transaction object |
|||
* @return {Object} object with all contract's method input variables |
|||
*/ |
|||
var getMethodInputParams = function (method, transaction) { |
|||
// do it with output formatter (cause we have to decode)
|
|||
var params = abi.formatOutput(method.inputs, '0x' + transaction.params[0].data.slice(10)); |
|||
|
|||
return method.inputs.reduce(function (acc, current, index) { |
|||
acc[current.name] = params[index]; |
|||
return acc; |
|||
}, {}); |
|||
}; |
|||
|
|||
/** |
|||
* Should be called when we want to evaluate natspec expression |
|||
* Replaces all natspec 'subexpressions' with evaluated value |
|||
* |
|||
* @method mapExpressionToEvaluate |
|||
* @param {String} expression to evaluate |
|||
* @param {Function} callback which is called to evaluate te expression |
|||
* @return {String} evaluated expression |
|||
*/ |
|||
var mapExpressionsToEvaluate = function (expression, cb) { |
|||
var evaluatedExpression = ""; |
|||
|
|||
// match everything in `` quotes
|
|||
var pattern = /\`(?:\\.|[^`\\])*\`/gim |
|||
var match; |
|||
var lastIndex = 0; |
|||
try { |
|||
while ((match = pattern.exec(expression)) !== null) { |
|||
var startIndex = pattern.lastIndex - match[0].length; |
|||
var toEval = match[0].slice(1, match[0].length - 1); |
|||
evaluatedExpression += expression.slice(lastIndex, startIndex); |
|||
var evaluatedPart = cb(toEval); |
|||
evaluatedExpression += evaluatedPart; |
|||
lastIndex = pattern.lastIndex; |
|||
} |
|||
|
|||
evaluatedExpression += expression.slice(lastIndex); |
|||
} |
|||
catch (err) { |
|||
throw new Error("Natspec evaluation failed, wrong input params"); |
|||
} |
|||
|
|||
return evaluatedExpression; |
|||
}; |
|||
|
|||
/** |
|||
* Should be called to evaluate single expression |
|||
* Is internally using javascript's 'eval' method |
|||
* |
|||
* @method evaluateExpression |
|||
* @param {String} expression which should be evaluated |
|||
* @param {Object} [call] object containing contract abi, transaction, called method |
|||
* @return {String} evaluated expression |
|||
* @throws exception if method is not found or we are trying to evaluate input params that does not exists |
|||
*/ |
|||
var evaluateExpression = function (expression, call) { |
|||
//var self = this;
|
|||
var context = {}; |
|||
|
|||
if (!!call) { |
|||
try { |
|||
var method = getMethodWithName(call.abi, call.method); |
|||
var params = getMethodInputParams(method, call.transaction); |
|||
copyToContext(params, context); |
|||
} |
|||
catch (err) { |
|||
throw new Error("Natspec evaluation failed, method does not exist"); |
|||
} |
|||
} |
|||
|
|||
var code = generateCode(context); |
|||
|
|||
var evaluatedExpression = mapExpressionsToEvaluate(expression, function (toEval) { |
|||
var fn = new Function("context", code + "return " + toEval + ";"); |
|||
return fn(context).toString(); |
|||
}); |
|||
|
|||
return evaluatedExpression; |
|||
}; |
|||
|
|||
/** |
|||
* Safe version of evaluateExpression |
|||
* Instead of throwing an exception it returns it as a string |
|||
* |
|||
* @method evaluateExpressionSafe |
|||
* @param {String} expression which should be evaluated |
|||
* @param {Object} [call] object containing contract abi, transaction, called method |
|||
* @return {String} evaluated expression |
|||
*/ |
|||
var evaluateExpressionSafe = function (expression, call) { |
|||
try { |
|||
return evaluateExpression(expression, call); |
|||
} |
|||
catch (err) { |
|||
return err.message; |
|||
} |
|||
}; |
|||
|
|||
return { |
|||
evaluateExpression: evaluateExpression, |
|||
evaluateExpressionSafe: evaluateExpressionSafe |
|||
}; |
|||
|
|||
})(); |
|||
|
|||
module.exports = natspec; |
|||
|
@ -0,0 +1,24 @@ |
|||
{ |
|||
"name": "natspec.js", |
|||
"version": "0.0.1", |
|||
"description": "Javascript Library used to evaluate natspec expressions", |
|||
"main": "natspec.js", |
|||
"scripts": { |
|||
"build": "cd dist && browserify -r ../natspec.js:natspec -i crypto -o natspec.js && uglifyjs natspec.js --source-map natspec.js.map -c -m -o natspec.min.js", |
|||
"test": "mocha", |
|||
"test-coveralls": "istanbul cover _mocha -- -R spec && cat coverage/lcov.info | coveralls --verbose" |
|||
}, |
|||
"author": "", |
|||
"dependencies": { |
|||
"ethereum.js": "ethereum/ethereum.js#master" |
|||
}, |
|||
"devDependencies": { |
|||
"browserify": "^9.0.3", |
|||
"chai": "^2.1.0", |
|||
"coveralls": "^2.11.2", |
|||
"istanbul": "^0.3.6", |
|||
"mocha": "^2.1.0", |
|||
"uglify-js": "^2.4.16" |
|||
}, |
|||
"license": "LGPL-3.0" |
|||
} |
@ -0,0 +1,149 @@ |
|||
var chai = require('chai'); |
|||
var natspec = require('../natspec.js'); |
|||
var assert = chai.assert; |
|||
|
|||
describe('natspec', function () { |
|||
it('should evaluate simple expression', function () { |
|||
// given
|
|||
var expression = "`x = 1` + `y = 2` will be equal `x + y`"; |
|||
|
|||
// when
|
|||
var result = natspec.evaluateExpression(expression); |
|||
var result2 = natspec.evaluateExpressionSafe(expression); |
|||
|
|||
// then
|
|||
assert.equal(result, "1 + 2 will be equal 3"); |
|||
assert.equal(result2, "1 + 2 will be equal 3"); |
|||
}); |
|||
|
|||
it('should evalute expression using input params', function () { |
|||
//given
|
|||
var expression = "Will multiply `a` by 7 and return `a * 7`."; |
|||
var method = 'multiply'; |
|||
var abi = [{ |
|||
"name": "multiply", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
}], |
|||
"outputs": [{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
}] |
|||
}]; |
|||
|
|||
var transaction = { |
|||
"jsonrpc": "2.0", |
|||
"method": "eth_call", |
|||
"params": [{ |
|||
"to": "0x8521742d3f456bd237e312d6e30724960f72517a", |
|||
"data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" |
|||
}], |
|||
"id": 6 |
|||
}; |
|||
|
|||
var call = { |
|||
method: method, |
|||
abi: abi, |
|||
transaction: transaction |
|||
}; |
|||
|
|||
// when
|
|||
var result = natspec.evaluateExpression(expression, call); |
|||
var result2 = natspec.evaluateExpressionSafe(expression, call); |
|||
|
|||
// then
|
|||
assert.equal(result, "Will multiply 122 by 7 and return 854."); |
|||
assert.equal(result2, "Will multiply 122 by 7 and return 854."); |
|||
}); |
|||
|
|||
it('should evalute expression using input params', function () { |
|||
//given
|
|||
var expression = "Will multiply `a` by 7 and return `a * 7`."; |
|||
var method = 'multiply'; |
|||
var abi = [{ |
|||
"name": "multiply", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [{ |
|||
"name": "b", |
|||
"type": "uint256" |
|||
}], |
|||
"outputs": [{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
}] |
|||
}]; |
|||
|
|||
var transaction = { |
|||
"jsonrpc": "2.0", |
|||
"method": "eth_call", |
|||
"params": [{ |
|||
"to": "0x8521742d3f456bd237e312d6e30724960f72517a", |
|||
"data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" |
|||
}], |
|||
"id": 6 |
|||
}; |
|||
|
|||
var call = { |
|||
method: method, |
|||
abi: abi, |
|||
transaction: transaction |
|||
}; |
|||
|
|||
// when
|
|||
var exceptionThrow = function () { natspec.evaluateExpression(expression, call);} |
|||
var result = natspec.evaluateExpressionSafe(expression, call); |
|||
|
|||
// then
|
|||
assert.equal(result, "Natspec evaluation failed, wrong input params"); |
|||
assert.throws(exceptionThrow, "Natspec evaluation failed, wrong input params"); |
|||
}); |
|||
|
|||
it('should evalute expression using input params', function () { |
|||
//given
|
|||
var expression = "Will multiply `a` by 7 and return `a * 7`."; |
|||
var method = 'multiply2'; |
|||
var abi = [{ |
|||
"name": "multiply", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
}], |
|||
"outputs": [{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
}] |
|||
}]; |
|||
|
|||
var transaction = { |
|||
"jsonrpc": "2.0", |
|||
"method": "eth_call", |
|||
"params": [{ |
|||
"to": "0x8521742d3f456bd237e312d6e30724960f72517a", |
|||
"data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" |
|||
}], |
|||
"id": 6 |
|||
}; |
|||
|
|||
var call = { |
|||
method: method, |
|||
abi: abi, |
|||
transaction: transaction |
|||
}; |
|||
|
|||
// when
|
|||
var exceptionThrow = function () { natspec.evaluateExpression(expression, call);} |
|||
var result = natspec.evaluateExpressionSafe(expression, call); |
|||
|
|||
// then
|
|||
assert.equal(result, "Natspec evaluation failed, method does not exist"); |
|||
assert.throws(exceptionThrow, "Natspec evaluation failed, method does not exist"); |
|||
}); |
|||
}); |
|||
|
|||
|
@ -0,0 +1,156 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Yann <yann@ethdev.com> |
|||
* @date 2015 |
|||
* Proxy used to filter a QML TableView. |
|||
*/ |
|||
|
|||
|
|||
#include "SortFilterProxyModel.h" |
|||
#include <QtDebug> |
|||
#include <QtQml> |
|||
|
|||
using namespace dev::mix; |
|||
|
|||
SortFilterProxyModel::SortFilterProxyModel(QObject* _parent) : QSortFilterProxyModel(_parent) |
|||
{ |
|||
connect(this, &SortFilterProxyModel::rowsInserted, this, &SortFilterProxyModel::countChanged); |
|||
connect(this, &SortFilterProxyModel::rowsRemoved, this, &SortFilterProxyModel::countChanged); |
|||
} |
|||
|
|||
int SortFilterProxyModel::count() const |
|||
{ |
|||
return rowCount(); |
|||
} |
|||
|
|||
QObject* SortFilterProxyModel::source() const |
|||
{ |
|||
return sourceModel(); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setSource(QObject* _source) |
|||
{ |
|||
setSourceModel(qobject_cast<QAbstractItemModel*>(_source)); |
|||
} |
|||
|
|||
QByteArray SortFilterProxyModel::sortRole() const |
|||
{ |
|||
return roleNames().value(QSortFilterProxyModel::sortRole()); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setSortRole(QByteArray const& _role) |
|||
{ |
|||
QSortFilterProxyModel::setSortRole(roleKey(_role)); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setSortOrder(Qt::SortOrder _order) |
|||
{ |
|||
QSortFilterProxyModel::sort(0, _order); |
|||
} |
|||
|
|||
QString SortFilterProxyModel::filterString() const |
|||
{ |
|||
return filterRegExp().pattern(); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setFilterString(QString const& _filter) |
|||
{ |
|||
setFilterRegExp(QRegExp(_filter, filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(filterSyntax()))); |
|||
} |
|||
|
|||
SortFilterProxyModel::FilterSyntax SortFilterProxyModel::filterSyntax() const |
|||
{ |
|||
return static_cast<FilterSyntax>(filterRegExp().patternSyntax()); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setFilterSyntax(SortFilterProxyModel::FilterSyntax _syntax) |
|||
{ |
|||
setFilterRegExp(QRegExp(filterString(), filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(_syntax))); |
|||
} |
|||
|
|||
QJSValue SortFilterProxyModel::get(int _idx) const |
|||
{ |
|||
QJSEngine *engine = qmlEngine(this); |
|||
QJSValue value = engine->newObject(); |
|||
if (_idx >= 0 && _idx < count()) |
|||
{ |
|||
QHash<int, QByteArray> roles = roleNames(); |
|||
QHashIterator<int, QByteArray> it(roles); |
|||
while (it.hasNext()) |
|||
{ |
|||
it.next(); |
|||
value.setProperty(QString::fromUtf8(it.value()), data(index(_idx, 0), it.key()).toString()); |
|||
} |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
int SortFilterProxyModel::roleKey(QByteArray const& _role) const |
|||
{ |
|||
QHash<int, QByteArray> roles = roleNames(); |
|||
QHashIterator<int, QByteArray> it(roles); |
|||
while (it.hasNext()) |
|||
{ |
|||
it.next(); |
|||
if (it.value() == _role) |
|||
return it.key(); |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
QHash<int, QByteArray> SortFilterProxyModel::roleNames() const |
|||
{ |
|||
if (QAbstractItemModel* source = sourceModel()) |
|||
return source->roleNames(); |
|||
return QHash<int, QByteArray>(); |
|||
} |
|||
|
|||
bool SortFilterProxyModel::filterAcceptsRow(int _sourceRow, QModelIndex const& _sourceParent) const |
|||
{ |
|||
QAbstractItemModel* model = sourceModel(); |
|||
QModelIndex sourceIndex = model->index(_sourceRow, 0, _sourceParent); |
|||
if (!sourceIndex.isValid()) |
|||
return true; |
|||
|
|||
QString keyType = model->data(sourceIndex, roleKey(type.toUtf8())).toString(); |
|||
QString keyContent = model->data(sourceIndex, roleKey(content.toUtf8())).toString(); |
|||
return keyType.contains(m_filterType) && keyContent.contains(m_filterContent); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setFilterType(QString const& _type) |
|||
{ |
|||
m_filterType = QRegExp(_type, filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(filterSyntax())); |
|||
setFilterRegExp(_type); |
|||
} |
|||
|
|||
QString SortFilterProxyModel::filterType() const |
|||
{ |
|||
return m_filterType.pattern(); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setFilterContent(QString const& _content) |
|||
{ |
|||
m_filterContent = QRegExp(_content, filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(filterSyntax())); |
|||
setFilterRegExp(_content); |
|||
} |
|||
|
|||
QString SortFilterProxyModel::filterContent() const |
|||
{ |
|||
return m_filterContent.pattern(); |
|||
} |
|||
|
@ -0,0 +1,97 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Yann <yann@ethdev.com> |
|||
* @date 2015 |
|||
* Proxy used to filter a QML TableView. |
|||
*/ |
|||
|
|||
|
|||
#pragma once |
|||
|
|||
#include <QtCore/qsortfilterproxymodel.h> |
|||
#include <QtQml/qjsvalue.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace mix |
|||
{ |
|||
|
|||
class SortFilterProxyModel: public QSortFilterProxyModel |
|||
{ |
|||
Q_OBJECT |
|||
Q_PROPERTY(int count READ count NOTIFY countChanged) |
|||
Q_PROPERTY(QObject* source READ source WRITE setSource) |
|||
|
|||
Q_PROPERTY(QByteArray sortRole READ sortRole WRITE setSortRole) |
|||
Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) |
|||
|
|||
Q_PROPERTY(QString filterContent READ filterContent WRITE setFilterContent) |
|||
Q_PROPERTY(QString filterType READ filterType WRITE setFilterType) |
|||
Q_PROPERTY(QString filterString READ filterString WRITE setFilterString) |
|||
Q_PROPERTY(FilterSyntax filterSyntax READ filterSyntax WRITE setFilterSyntax) |
|||
|
|||
Q_ENUMS(FilterSyntax) |
|||
|
|||
public: |
|||
explicit SortFilterProxyModel(QObject* _parent = 0); |
|||
|
|||
QObject* source() const; |
|||
void setSource(QObject* _source); |
|||
|
|||
QByteArray sortRole() const; |
|||
void setSortRole(QByteArray const& _role); |
|||
|
|||
void setSortOrder(Qt::SortOrder _order); |
|||
|
|||
QString filterContent() const; |
|||
void setFilterContent(QString const& _content); |
|||
QString filterType() const; |
|||
void setFilterType(QString const& _type); |
|||
|
|||
QString filterString() const; |
|||
void setFilterString(QString const& _filter); |
|||
|
|||
enum FilterSyntax { |
|||
RegExp, |
|||
Wildcard, |
|||
FixedString |
|||
}; |
|||
|
|||
FilterSyntax filterSyntax() const; |
|||
void setFilterSyntax(FilterSyntax _syntax); |
|||
|
|||
int count() const; |
|||
Q_INVOKABLE QJSValue get(int _index) const; |
|||
|
|||
signals: |
|||
void countChanged(); |
|||
|
|||
protected: |
|||
int roleKey(QByteArray const& _role) const; |
|||
QHash<int, QByteArray> roleNames() const; |
|||
bool filterAcceptsRow(int _sourceRow, QModelIndex const& _sourceParent) const; |
|||
|
|||
private: |
|||
QRegExp m_filterType; |
|||
QRegExp m_filterContent; |
|||
const QString type = "type"; |
|||
const QString content = "content"; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,302 @@ |
|||
import QtQuick 2.0 |
|||
import QtQuick.Layouts 1.0 |
|||
import QtQuick.Controls 1.1 |
|||
import QtQuick.Controls.Styles 1.3 |
|||
import org.ethereum.qml.SortFilterProxyModel 1.0 |
|||
import "." |
|||
|
|||
Rectangle |
|||
{ |
|||
function push(_level, _type, _content) |
|||
{ |
|||
_content = _content.replace(/\n/g, " ") |
|||
logsModel.insert(0, { "type": _type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss dd.MM.yyyy"), "content": _content, "level": _level }); |
|||
} |
|||
|
|||
anchors.fill: parent |
|||
radius: 5 |
|||
color: LogsPaneStyle.generic.layout.backgroundColor |
|||
border.color: LogsPaneStyle.generic.layout.borderColor |
|||
border.width: LogsPaneStyle.generic.layout.borderWidth |
|||
ColumnLayout { |
|||
z: 2 |
|||
height: parent.height |
|||
width: parent.width |
|||
spacing: 0 |
|||
Row |
|||
{ |
|||
id: rowAction |
|||
Layout.preferredHeight: LogsPaneStyle.generic.layout.headerHeight |
|||
height: LogsPaneStyle.generic.layout.headerHeight |
|||
anchors.leftMargin: LogsPaneStyle.generic.layout.leftMargin |
|||
anchors.left: parent.left |
|||
spacing: LogsPaneStyle.generic.layout.headerButtonSpacing |
|||
Button |
|||
{ |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
action: clearAction |
|||
iconSource: "qrc:/qml/img/broom.png" |
|||
} |
|||
|
|||
Action { |
|||
id: clearAction |
|||
enabled: logsModel.count > 0 |
|||
tooltip: qsTr("Clear") |
|||
onTriggered: { |
|||
logsModel.clear() |
|||
} |
|||
} |
|||
|
|||
Button |
|||
{ |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
action: copytoClipBoardAction |
|||
iconSource: "qrc:/qml/img/copy.png" |
|||
} |
|||
|
|||
Action { |
|||
id: copytoClipBoardAction |
|||
enabled: logsModel.count > 0 |
|||
tooltip: qsTr("Copy to Clipboard") |
|||
onTriggered: { |
|||
var content = ""; |
|||
for (var k = 0; k < logsModel.count; k++) |
|||
{ |
|||
var log = logsModel.get(k); |
|||
content += log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content + "\n"; |
|||
} |
|||
appContext.toClipboard(content); |
|||
} |
|||
} |
|||
|
|||
Rectangle { |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
width: 1; |
|||
height: parent.height - 10 |
|||
color : "#808080" |
|||
} |
|||
|
|||
ToolButton { |
|||
id: javascriptButton |
|||
checkable: true |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
checked: true |
|||
onCheckedChanged: { |
|||
proxyModel.toogleFilter("javascript") |
|||
} |
|||
tooltip: qsTr("JavaScript") |
|||
style: |
|||
ButtonStyle { |
|||
label: |
|||
Item { |
|||
DefaultLabel { |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
color: LogsPaneStyle.generic.layout.logLabelColor |
|||
anchors.centerIn: parent |
|||
text: qsTr("JavaScript") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
ToolButton { |
|||
id: runButton |
|||
checkable: true |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
checked: true |
|||
onCheckedChanged: { |
|||
proxyModel.toogleFilter("run") |
|||
} |
|||
tooltip: qsTr("Run") |
|||
style: |
|||
ButtonStyle { |
|||
label: |
|||
Item { |
|||
DefaultLabel { |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
color: LogsPaneStyle.generic.layout.logLabelColor |
|||
anchors.centerIn: parent |
|||
text: qsTr("Run") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
ToolButton { |
|||
id: stateButton |
|||
checkable: true |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
checked: true |
|||
onCheckedChanged: { |
|||
proxyModel.toogleFilter("state") |
|||
} |
|||
tooltip: qsTr("State") |
|||
style: |
|||
ButtonStyle { |
|||
label: |
|||
Item { |
|||
DefaultLabel { |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
color: "#5391d8" |
|||
anchors.centerIn: parent |
|||
text: qsTr("State") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
ToolButton { |
|||
id: compilationButton |
|||
checkable: true |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
checked: false |
|||
onCheckedChanged: { |
|||
proxyModel.toogleFilter("compilation") |
|||
} |
|||
tooltip: qsTr("Compilation") |
|||
style: |
|||
ButtonStyle { |
|||
label: |
|||
Item { |
|||
DefaultLabel { |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
color: "#5391d8" |
|||
anchors.centerIn: parent |
|||
text: qsTr("Compilation") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
DefaultTextField |
|||
{ |
|||
id: searchBox |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
width: LogsPaneStyle.generic.layout.headerInputWidth |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
font.italic: true |
|||
onTextChanged: { |
|||
proxyModel.search(text); |
|||
} |
|||
} |
|||
} |
|||
|
|||
ListModel { |
|||
id: logsModel |
|||
} |
|||
|
|||
TableView { |
|||
id: logsTable |
|||
clip: true |
|||
Layout.fillWidth: true |
|||
Layout.preferredHeight: parent.height - rowAction.height |
|||
headerVisible : false |
|||
onDoubleClicked: |
|||
{ |
|||
var log = logsModel.get((logsTable.currentRow)); |
|||
if (log) |
|||
appContext.toClipboard(log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content); |
|||
} |
|||
|
|||
model: SortFilterProxyModel { |
|||
id: proxyModel |
|||
source: logsModel |
|||
property var roles: ["-", "javascript", "run", "state"] |
|||
|
|||
Component.onCompleted: { |
|||
filterType = regEx(proxyModel.roles); |
|||
} |
|||
|
|||
function search(_value) |
|||
{ |
|||
filterContent = _value; |
|||
} |
|||
|
|||
function toogleFilter(_value) |
|||
{ |
|||
var count = roles.length; |
|||
for (var i in roles) |
|||
{ |
|||
if (roles[i] === _value) |
|||
{ |
|||
roles.splice(i, 1); |
|||
break; |
|||
} |
|||
} |
|||
if (count === roles.length) |
|||
roles.push(_value); |
|||
|
|||
filterType = regEx(proxyModel.roles); |
|||
} |
|||
|
|||
function regEx(_value) |
|||
{ |
|||
return "(?:" + roles.join('|') + ")"; |
|||
} |
|||
|
|||
filterType: "(?:javascript|run|state)" |
|||
filterContent: "" |
|||
filterSyntax: SortFilterProxyModel.RegExp |
|||
filterCaseSensitivity: Qt.CaseInsensitive |
|||
} |
|||
TableViewColumn |
|||
{ |
|||
role: "date" |
|||
title: qsTr("date") |
|||
width: LogsPaneStyle.generic.layout.dateWidth |
|||
delegate: itemDelegate |
|||
} |
|||
TableViewColumn |
|||
{ |
|||
role: "type" |
|||
title: qsTr("type") |
|||
width: LogsPaneStyle.generic.layout.typeWidth |
|||
delegate: itemDelegate |
|||
} |
|||
TableViewColumn |
|||
{ |
|||
role: "content" |
|||
title: qsTr("content") |
|||
width: LogsPaneStyle.generic.layout.contentWidth |
|||
delegate: itemDelegate |
|||
} |
|||
|
|||
rowDelegate: Item { |
|||
Rectangle { |
|||
width: logsTable.width - 4 |
|||
height: 17 |
|||
color: styleData.alternate ? "transparent" : LogsPaneStyle.generic.layout.logAlternateColor |
|||
} |
|||
} |
|||
} |
|||
|
|||
Component { |
|||
id: itemDelegate |
|||
DefaultLabel { |
|||
text: styleData.value; |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-1) |
|||
color: { |
|||
if (proxyModel.get(styleData.row).level === "error") |
|||
return "red"; |
|||
else if (proxyModel.get(styleData.row).level === "warning") |
|||
return "orange"; |
|||
else |
|||
return "#808080"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
pragma Singleton |
|||
import QtQuick 2.0 |
|||
|
|||
QtObject { |
|||
|
|||
function absoluteSize(rel) |
|||
{ |
|||
return systemPointSize + rel; |
|||
} |
|||
|
|||
property QtObject generic: QtObject { |
|||
property QtObject layout: QtObject { |
|||
property string backgroundColor: "#f7f7f7" |
|||
property string borderColor: "#5391d8" |
|||
property int borderWidth: 1 |
|||
property int headerHeight: 35 |
|||
property int headerButtonSpacing: 5 |
|||
property int leftMargin: 10 |
|||
property int headerButtonHeight: 30 |
|||
property string logLabelColor: "#5391d8" |
|||
property string logLabelFont: "sans serif" |
|||
property int headerInputWidth: 200 |
|||
property int dateWidth: 150 |
|||
property int typeWidth: 80 |
|||
property int contentWidth: 700 |
|||
property string logAlternateColor: "#f0f0f0" |
|||
} |
|||
} |
|||
} |
After Width: | Height: | Size: 501 B |
After Width: | Height: | Size: 342 B |
@ -0,0 +1,32 @@ |
|||
# usage |
|||
# ./pullsubtree [repository branch] [repository2 branch2] |
|||
# |
|||
# example |
|||
# ./pullSubtree evmjit master |
|||
# ./pullSubtree ethereumjs develop |
|||
# ./pullSubtree evmjit master ethereumjs master |
|||
|
|||
evmjit_repo="https://github.com/ethereum/evmjit" |
|||
evmjit_location="evmjit" |
|||
|
|||
ethereumjs_repo="https://github.com/ethereum/ethereum.js" |
|||
ethereumjs_location="libjsqrc/ethereumjs" |
|||
|
|||
natspecjs_repo="https://github.com/ethereum/natspec.js" |
|||
natspecjs_location="libnatspec/natspecjs" |
|||
|
|||
while [ "$1" != "" ]; do |
|||
case $1 in |
|||
evmjit | ethereumjs | natspecjs ) |
|||
REPO="${1}_repo" |
|||
REPO=${!REPO} |
|||
LOCATION="${1}_location" |
|||
LOCATION=${!LOCATION} |
|||
shift |
|||
BRANCH=$1 |
|||
git subtree pull --prefix=${LOCATION} ${REPO} ${BRANCH} --squash |
|||
;; |
|||
esac |
|||
shift |
|||
done |
|||
|
Loading…
Reference in new issue