Browse Source

API alterations. Mostly s/transaction/message/.

cl-refactor
Gav Wood 11 years ago
parent
commit
ad75ebaad3
  1. 16
      alethzero/MainWin.cpp
  2. 4
      alethzero/MainWin.h
  3. 30
      libethereum/Client.cpp
  4. 83
      libethereum/Client.h
  5. 60
      libqethereum/QEthereum.cpp
  6. 29
      libqethereum/QEthereum.h

16
alethzero/MainWin.cpp

@ -232,7 +232,7 @@ void Main::onKeysChanged()
installBalancesWatch();
}
unsigned Main::installWatch(eth::TransactionFilter const& _tf, std::function<void()> const& _f)
unsigned Main::installWatch(eth::MessageFilter const& _tf, std::function<void()> const& _f)
{
auto ret = m_client->installWatch(_tf);
m_handlers[ret] = _f;
@ -248,27 +248,27 @@ unsigned Main::installWatch(eth::h256 _tf, std::function<void()> const& _f)
void Main::installWatches()
{
installWatch(eth::TransactionFilter().altered(c_config, 0), [=](){ installNameRegWatch(); });
installWatch(eth::TransactionFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); });
installWatch(eth::NewPendingFilter, [=](){ onNewPending(); });
installWatch(eth::NewBlockFilter, [=](){ onNewBlock(); });
installWatch(eth::MessageFilter().altered(c_config, 0), [=](){ installNameRegWatch(); });
installWatch(eth::MessageFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); });
installWatch(eth::PendingChangedFilter, [=](){ onNewPending(); });
installWatch(eth::ChainChangedFilter, [=](){ onNewBlock(); });
}
void Main::installNameRegWatch()
{
m_client->uninstallWatch(m_nameRegFilter);
m_nameRegFilter = installWatch(eth::TransactionFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); });
m_nameRegFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); });
}
void Main::installCurrenciesWatch()
{
m_client->uninstallWatch(m_currenciesFilter);
m_currenciesFilter = installWatch(eth::TransactionFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); });
m_currenciesFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); });
}
void Main::installBalancesWatch()
{
eth::TransactionFilter tf;
eth::MessageFilter tf;
vector<Address> altCoins;
Address coinsAddr = right160(m_client->stateAt(c_config, 1));

4
alethzero/MainWin.h

@ -39,7 +39,7 @@ class Main;
namespace eth {
class Client;
class State;
class TransactionFilter;
class MessageFilter;
}
class QQuickView;
@ -166,7 +166,7 @@ private:
eth::u256 value() const;
eth::u256 gasPrice() const;
unsigned installWatch(eth::TransactionFilter const& _tf, std::function<void()> const& _f);
unsigned installWatch(eth::MessageFilter const& _tf, std::function<void()> const& _f);
unsigned installWatch(eth::h256 _tf, std::function<void()> const& _f);
void onNewPending();

30
libethereum/Client.cpp

@ -30,12 +30,12 @@
using namespace std;
using namespace eth;
void TransactionFilter::fillStream(RLPStream& _s) const
void MessageFilter::fillStream(RLPStream& _s) const
{
_s.appendList(8) << m_from << m_to << m_stateAltered << m_altered << m_earliest << m_latest << m_max << m_skip;
}
h256 TransactionFilter::sha3() const
h256 MessageFilter::sha3() const
{
RLPStream s;
fillStream(s);
@ -125,7 +125,7 @@ void Client::clearPending()
h256Set changeds;
for (unsigned i = 0; i < m_postMine.pending().size(); ++i)
appendFromNewPending(m_postMine.bloom(i), changeds);
changeds.insert(NewPendingFilter);
changeds.insert(PendingChangedFilter);
m_postMine = m_preMine;
noteChanged(changeds);
}
@ -133,12 +133,12 @@ void Client::clearPending()
unsigned Client::installWatch(h256 _h)
{
auto ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0;
m_watches[ret] = Watch(_h);
m_watches[ret] = ClientWatch(_h);
cwatch << "+++" << ret << _h;
return ret;
}
unsigned Client::installWatch(TransactionFilter const& _f)
unsigned Client::installWatch(MessageFilter const& _f)
{
lock_guard<mutex> l(m_filterLock);
@ -444,8 +444,8 @@ void Client::work(bool _justQueue)
{
for (auto h: hs)
appendFromNewBlock(h, changeds);
changeds.insert(NewBlockFilter);
//changeds.insert(NewPendingFilter); // if we mined the new block, then we've probably reset the pending transactions.
changeds.insert(ChainChangedFilter);
//changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions.
}
}
}
@ -474,7 +474,7 @@ void Client::work(bool _justQueue)
{
for (auto i: newBlocks)
appendFromNewBlock(i, changeds);
changeds.insert(NewBlockFilter);
changeds.insert(ChainChangedFilter);
}
x_stateDB.lock();
if (newBlocks.size())
@ -487,7 +487,7 @@ void Client::work(bool _justQueue)
cnote << "New block on chain: Restarting mining operation.";
m_restartMining = true; // need to re-commit to mine.
m_postMine = m_preMine;
changeds.insert(NewPendingFilter);
changeds.insert(PendingChangedFilter);
}
// returns h256s as blooms, once for each transaction.
@ -497,7 +497,7 @@ void Client::work(bool _justQueue)
{
for (auto i: newPendingBlooms)
appendFromNewPending(i, changeds);
changeds.insert(NewPendingFilter);
changeds.insert(PendingChangedFilter);
if (m_doMine)
cnote << "Additional transaction ready: Restarting mining operation.";
@ -594,7 +594,7 @@ bytes Client::codeAt(Address _a, int _block) const
return asOf(_block).code(_a);
}
bool TransactionFilter::matches(h256 _bloom) const
bool MessageFilter::matches(h256 _bloom) const
{
auto have = [=](Address const& a) { return _bloom.contains(a.bloom()); };
if (m_from.size())
@ -627,7 +627,7 @@ bool TransactionFilter::matches(h256 _bloom) const
return true;
}
bool TransactionFilter::matches(State const& _s, unsigned _i) const
bool MessageFilter::matches(State const& _s, unsigned _i) const
{
h256 b = _s.changesFromPending(_i).bloom();
if (!matches(b))
@ -658,14 +658,14 @@ bool TransactionFilter::matches(State const& _s, unsigned _i) const
return true;
}
PastMessages TransactionFilter::matches(Manifest const& _m, unsigned _i) const
PastMessages MessageFilter::matches(Manifest const& _m, unsigned _i) const
{
PastMessages ret;
matches(_m, vector<unsigned>(1, _i), _m.from, PastMessages(), ret);
return ret;
}
bool TransactionFilter::matches(Manifest const& _m, vector<unsigned> _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const
bool MessageFilter::matches(Manifest const& _m, vector<unsigned> _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const
{
bool ret;
@ -705,7 +705,7 @@ bool TransactionFilter::matches(Manifest const& _m, vector<unsigned> _p, Address
return ret;
}
PastMessages Client::transactions(TransactionFilter const& _f) const
PastMessages Client::messages(MessageFilter const& _f) const
{
PastMessages ret;
unsigned begin = min<unsigned>(m_bc.number(), (unsigned)_f.latest());

83
libethereum/Client.h

@ -25,6 +25,7 @@
#include <mutex>
#include <list>
#include <atomic>
#include <boost/utility.hpp>
#include <libethential/Common.h>
#include <libethential/CommonIO.h>
#include <libevm/FeeStructure.h>
@ -93,10 +94,10 @@ struct PastMessage
typedef std::vector<PastMessage> PastMessages;
class TransactionFilter
class MessageFilter
{
public:
TransactionFilter(int _earliest = 0, int _latest = -1, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {}
MessageFilter(int _earliest = 0, int _latest = -1, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {}
void fillStream(RLPStream& _s) const;
h256 sha3() const;
@ -109,14 +110,14 @@ public:
bool matches(State const& _s, unsigned _i) const;
PastMessages matches(Manifest const& _m, unsigned _i) const;
TransactionFilter from(Address _a) { m_from.insert(_a); return *this; }
TransactionFilter to(Address _a) { m_to.insert(_a); return *this; }
TransactionFilter altered(Address _a, u256 _l) { m_stateAltered.insert(std::make_pair(_a, _l)); return *this; }
TransactionFilter altered(Address _a) { m_altered.insert(_a); return *this; }
TransactionFilter withMax(unsigned _m) { m_max = _m; return *this; }
TransactionFilter withSkip(unsigned _m) { m_skip = _m; return *this; }
TransactionFilter withEarliest(int _e) { m_earliest = _e; return *this; }
TransactionFilter withLatest(int _e) { m_latest = _e; return *this; }
MessageFilter from(Address _a) { m_from.insert(_a); return *this; }
MessageFilter to(Address _a) { m_to.insert(_a); return *this; }
MessageFilter altered(Address _a, u256 _l) { m_stateAltered.insert(std::make_pair(_a, _l)); return *this; }
MessageFilter altered(Address _a) { m_altered.insert(_a); return *this; }
MessageFilter withMax(unsigned _m) { m_max = _m; return *this; }
MessageFilter withSkip(unsigned _m) { m_skip = _m; return *this; }
MessageFilter withEarliest(int _e) { m_earliest = _e; return *this; }
MessageFilter withLatest(int _e) { m_latest = _e; return *this; }
private:
bool matches(Manifest const& _m, std::vector<unsigned> _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const;
@ -133,19 +134,19 @@ private:
struct InstalledFilter
{
InstalledFilter(TransactionFilter const& _f): filter(_f) {}
InstalledFilter(MessageFilter const& _f): filter(_f) {}
TransactionFilter filter;
MessageFilter filter;
unsigned refCount = 1;
};
static const h256 NewPendingFilter = u256(0);
static const h256 NewBlockFilter = u256(1);
static const h256 PendingChangedFilter = u256(0);
static const h256 ChainChangedFilter = u256(1);
struct Watch
struct ClientWatch
{
Watch() {}
explicit Watch(h256 _id): id(_id) {}
ClientWatch() {}
explicit ClientWatch(h256 _id): id(_id) {}
h256 id;
unsigned changes = 1;
@ -192,6 +193,7 @@ public:
// [NEW API]
int getDefault() const { return m_default; }
void setDefault(int _block) { m_default = _block; }
u256 balanceAt(Address _a) const { return balanceAt(_a, m_default); }
@ -206,14 +208,14 @@ public:
bytes codeAt(Address _a, int _block) const;
std::map<u256, u256> storageAt(Address _a, int _block) const;
unsigned installWatch(TransactionFilter const& _filter);
unsigned installWatch(MessageFilter const& _filter);
unsigned installWatch(h256 _filterId);
void uninstallWatch(unsigned _watchId);
bool peekWatch(unsigned _watchId) const { std::lock_guard<std::mutex> l(m_filterLock); try { return m_watches.at(_watchId).changes != 0; } catch (...) { return false; } }
bool checkWatch(unsigned _watchId) { std::lock_guard<std::mutex> l(m_filterLock); bool ret = false; try { ret = m_watches.at(_watchId).changes != 0; m_watches.at(_watchId).changes = 0; } catch (...) {} return ret; }
PastMessages transactions(unsigned _watchId) const { try { std::lock_guard<std::mutex> l(m_filterLock); return transactions(m_filters.at(m_watches.at(_watchId).id).filter); } catch (...) { return PastMessages(); } }
PastMessages transactions(TransactionFilter const& _filter) const;
PastMessages messages(unsigned _watchId) const { try { std::lock_guard<std::mutex> l(m_filterLock); return messages(m_filters.at(m_watches.at(_watchId).id).filter); } catch (...) { return PastMessages(); } }
PastMessages messages(MessageFilter const& _filter) const;
// [EXTRA API]:
@ -351,9 +353,48 @@ private:
mutable std::mutex m_filterLock;
std::map<h256, InstalledFilter> m_filters;
std::map<unsigned, Watch> m_watches;
std::map<unsigned, ClientWatch> m_watches;
int m_default = -1;
};
class Watch;
}
namespace std { void swap(eth::Watch& _a, eth::Watch& _b); }
namespace eth
{
class Watch: public boost::noncopyable
{
friend void std::swap(Watch& _a, Watch& _b);
public:
Watch() {}
Watch(Client& _c, h256 _f): m_c(&_c), m_id(_c.installWatch(_f)) {}
Watch(Client& _c, MessageFilter const& _tf): m_c(&_c), m_id(_c.installWatch(_tf)) {}
~Watch() { if (m_c) m_c->uninstallWatch(m_id); }
bool check() { return m_c ? m_c->checkWatch(m_id) : false; }
bool peek() { return m_c ? m_c->peekWatch(m_id) : false; }
PastMessages messages() const { return m_c->messages(m_id); }
private:
Client* m_c;
unsigned m_id;
};
}
namespace std
{
inline void swap(eth::Watch& _a, eth::Watch& _b)
{
swap(_a.m_c, _b.m_c);
swap(_a.m_id, _b.m_id);
}
}

60
libqethereum/QEthereum.cpp

@ -180,54 +180,50 @@ void QEthereum::setCoinbase(QString _a)
}
}
QString QEthereum::balanceAt(QString _a) const
void QEthereum::setDefault(int _block)
{
return m_client ? toQJS(client()->postState().balance(toAddress(_a))) : "";
}
QString QEthereum::storageAt(QString _a, QString _p) const
{
return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p))) : "";
if (m_client)
m_client->setDefault(_block);
}
double QEthereum::txCountAt(QString _a) const
int QEthereum::getDefault() const
{
return m_client ? (double)client()->countAt(toAddress(_a)) : 0.0;
return m_client ? m_client->getDefault() : 0;
}
bool QEthereum::isContractAt(QString _a) const
QString QEthereum::balanceAt(QString _a) const
{
return m_client ? client()->codeAt(toAddress(_a)).size() : false;
return m_client ? toQJS(client()->balanceAt(toAddress(_a))) : "";
}
u256 QEthereum::balanceAt(Address _a) const
QString QEthereum::balanceAt(QString _a, int _block) const
{
return m_client ? client()->balanceAt(_a) : 0;
return m_client ? toQJS(client()->balanceAt(toAddress(_a), _block)) : "";
}
double QEthereum::txCountAt(Address _a) const
QString QEthereum::stateAt(QString _a, QString _p) const
{
return m_client ? (double)client()->countAt(_a) : 0.0;
return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p))) : "";
}
bool QEthereum::isContractAt(Address _a) const
QString QEthereum::stateAt(QString _a, QString _p, int _block) const
{
return m_client ? client()->codeAt(_a).size() : false;
return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p), _block)) : "";
}
QString QEthereum::balanceAt(QString _a, int _block) const
QString QEthereum::codeAt(QString _a) const
{
return m_client ? toQJS(client()->balanceAt(toAddress(_a), _block)) : "";
return m_client ? ::fromBinary(client()->codeAt(toAddress(_a))) : "";
}
QString QEthereum::stateAt(QString _a, QString _p, int _block) const
QString QEthereum::codeAt(QString _a, int _block) const
{
return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p), _block)) : "";
return m_client ? ::fromBinary(client()->codeAt(toAddress(_a), _block)) : "";
}
QString QEthereum::codeAt(QString _a, int _block) const
double QEthereum::countAt(QString _a) const
{
return m_client ? ::fromBinary(client()->codeAt(toAddress(_a), _block)) : "";
return m_client ? (double)(uint64_t)client()->countAt(toAddress(_a)) : 0;
}
double QEthereum::countAt(QString _a, int _block) const
@ -235,9 +231,9 @@ double QEthereum::countAt(QString _a, int _block) const
return m_client ? (double)(uint64_t)client()->countAt(toAddress(_a), _block) : 0;
}
static eth::TransactionFilter toTransactionFilter(QString _json)
static eth::MessageFilter toMessageFilter(QString _json)
{
eth::TransactionFilter filter;
eth::MessageFilter filter;
QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object();
if (f.contains("earliest"))
@ -305,9 +301,9 @@ static QString toJson(eth::PastMessages const& _pms)
return QString::fromUtf8(QJsonDocument(jsonArray).toJson());
}
QString QEthereum::getTransactions(QString _json) const
QString QEthereum::getMessages(QString _json) const
{
return m_client ? toJson(m_client->transactions(toTransactionFilter(_json))) : "";
return m_client ? toJson(m_client->messages(toMessageFilter(_json))) : "";
}
bool QEthereum::isMining() const
@ -369,20 +365,20 @@ unsigned QEthereum::newWatch(QString _json)
return (unsigned)-1;
unsigned ret;
if (_json == "chainChanged")
ret = m_client->installWatch(eth::NewBlockFilter);
ret = m_client->installWatch(eth::ChainChangedFilter);
else if (_json == "pendingChanged")
ret = m_client->installWatch(eth::NewPendingFilter);
ret = m_client->installWatch(eth::PendingChangedFilter);
else
ret = m_client->installWatch(toTransactionFilter(_json));
ret = m_client->installWatch(toMessageFilter(_json));
m_watches.push_back(ret);
return ret;
}
QString QEthereum::watchTransactions(unsigned _w)
QString QEthereum::watchMessages(unsigned _w)
{
if (!m_client)
return "";
return toJson(m_client->transactions(_w));
return toJson(m_client->messages(_w));
}
void QEthereum::killWatch(unsigned _w)

29
libqethereum/QEthereum.h

@ -112,24 +112,24 @@ public:
Q_INVOKABLE QString fromBinary(QString _s) const { return ::fromBinary(_s); }
Q_INVOKABLE QString toDecimal(QString _s) const { return ::toDecimal(_s); }
// [OLD API] - Don't use this.
Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a) const;
Q_INVOKABLE QString/*eth::u256*/ storageAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p) const;
Q_INVOKABLE double txCountAt(QString/*eth::Address*/ _a) const;
Q_INVOKABLE bool isContractAt(QString/*eth::Address*/ _a) const;
// [NEW API] - Use this instead.
Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a, int _block) const;
Q_INVOKABLE double countAt(QString/*eth::Address*/ _a, int _block) const;
Q_INVOKABLE QString/*eth::u256*/ stateAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p, int _block) const;
Q_INVOKABLE QString/*eth::u256*/ codeAt(QString/*eth::Address*/ _a, int _block) const;
Q_INVOKABLE QString/*json*/ getTransactions(QString _attribs/*json*/) const;
Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a) const;
Q_INVOKABLE double countAt(QString/*eth::Address*/ _a) const;
Q_INVOKABLE QString/*eth::u256*/ stateAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p) const;
Q_INVOKABLE QString/*eth::u256*/ codeAt(QString/*eth::Address*/ _a) const;
Q_INVOKABLE QString/*json*/ getMessages(QString _attribs/*json*/) const;
Q_INVOKABLE QString doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice);
Q_INVOKABLE void doTransact(QString _secret, QString _amount, QString _dest, QString _data, QString _gas, QString _gasPrice);
Q_INVOKABLE unsigned newWatch(QString _json);
Q_INVOKABLE QString watchTransactions(unsigned _w);
Q_INVOKABLE QString watchMessages(unsigned _w);
Q_INVOKABLE void killWatch(unsigned _w);
void clearWatches();
@ -138,11 +138,7 @@ public:
QString/*eth::Address*/ coinbase() const;
QString/*eth::u256*/ gasPrice() const { return toQJS(10 * eth::szabo); }
QString number() const;
eth::u256 balanceAt(eth::Address _a) const;
double txCountAt(eth::Address _a) const;
bool isContractAt(eth::Address _a) const;
int getDefault() const;
QString/*eth::KeyPair*/ key() const;
QStringList/*list of eth::KeyPair*/ keys() const;
@ -155,6 +151,7 @@ public slots:
void setCoinbase(QString/*eth::Address*/);
void setMining(bool _l);
void setListening(bool _l);
void setDefault(int _block);
/// Check to see if anything has changed, fire off signals if so.
/// @note Must be called in the QObject's thread.
@ -176,6 +173,7 @@ private:
Q_PROPERTY(bool mining READ isMining WRITE setMining NOTIFY netChanged)
Q_PROPERTY(bool listening READ isListening WRITE setListening NOTIFY netChanged)
Q_PROPERTY(unsigned peerCount READ peerCount NOTIFY miningChanged)
Q_PROPERTY(int defaultBlock READ getDefault NOTIFY setDefault)
eth::Client* m_client;
std::vector<unsigned> m_watches;
@ -187,13 +185,14 @@ private:
frame->disconnect(); \
frame->addToJavaScriptWindowObject("env", env, QWebFrame::QtOwnership); \
frame->addToJavaScriptWindowObject("eth", eth, QWebFrame::ScriptOwnership); \
frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { eth.killWatch(w); }; ret.changed = function(f) { eth.watchChanged.connect(function(nw) { if (nw == ww) f() }); }; ret.transactions = function() { return JSON.parse(eth.watchTransactions(this.w)) }; return ret; }"); \
frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { eth.killWatch(w); }; ret.changed = function(f) { eth.watchChanged.connect(function(nw) { if (nw == ww) f() }); }; ret.messages = function() { return JSON.parse(eth.watchMessages(this.w)) }; return ret; }"); \
frame->evaluateJavaScript("eth.watch = function(a) { return eth.makeWatch(JSON.stringify(a)) }"); \
frame->evaluateJavaScript("eth.watchChain = function() { return eth.makeWatch('chainChanged') }"); \
frame->evaluateJavaScript("eth.watchPending = function() { return eth.makeWatch('pendingChanged') }"); \
frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \
frame->evaluateJavaScript("eth.transact = function(s, v, t, d, g, p, f) { eth.doTransact(s, v, t, d, g, p); if (f) f() }"); \
frame->evaluateJavaScript("eth.transactions = function(a) { return JSON.parse(eth.getTransactions(JSON.stringify(a))); }"); \
frame->evaluateJavaScript("eth.messages = function(a) { return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \
frame->evaluateJavaScript("eth.transactions = function(a) { env.warn('THIS CALL IS DEPRECATED. USE eth.messages INSTEAD.'); return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \
frame->evaluateJavaScript("String.prototype.pad = function(l, r) { return eth.pad(this, l, r) }"); \
frame->evaluateJavaScript("String.prototype.bin = function() { return eth.toBinary(this) }"); \
frame->evaluateJavaScript("String.prototype.unbin = function(l) { return eth.fromBinary(this) }"); \

Loading…
Cancel
Save