diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 815710d0..fc19f56d 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -82,14 +82,19 @@ namespace graphene { namespace app { return result; } - vector> database_api::lookup_asset_symbols(const vector& symbols)const + vector> database_api::lookup_asset_symbols(const vector& symbols_or_ids)const { const auto& assets_by_symbol = _db.get_index_type().indices().get(); vector > result; - result.reserve(symbols.size()); - std::transform(symbols.begin(), symbols.end(), std::back_inserter(result), - [&assets_by_symbol](const string& symbol) -> optional { - auto itr = assets_by_symbol.find(symbol); + result.reserve(symbols_or_ids.size()); + std::transform(symbols_or_ids.begin(), symbols_or_ids.end(), std::back_inserter(result), + [this, &assets_by_symbol](const string& symbol_or_id) -> optional { + if( !symbol_or_id.empty() && std::isdigit(symbol_or_id[0]) ) + { + auto ptr = _db.find(variant(symbol_or_id).as()); + return ptr == nullptr? optional() : *ptr; + } + auto itr = assets_by_symbol.find(symbol_or_id); return itr == assets_by_symbol.end()? optional() : *itr; }); return result; diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 75d3ac52..d61d8d12 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -110,12 +110,12 @@ namespace graphene { namespace app { vector> lookup_account_names(const vector& account_names)const; /** * @brief Get a list of assets by symbol - * @param asset_symbols Symbols of the assets to retrieve - * @return The assets corresponding to the provided symbols + * @param asset_symbols Symbols or stringified IDs of the assets to retrieve + * @return The assets corresponding to the provided symbols or IDs * * This function has semantics identical to @ref get_objects */ - vector> lookup_asset_symbols(const vector& asset_symbols)const; + vector> lookup_asset_symbols(const vector& symbols_or_ids)const; /** * @brief Get an account's balances in various assets diff --git a/programs/light_client/ClientDataModel.cpp b/programs/light_client/ClientDataModel.cpp index cd76b925..2997016f 100644 --- a/programs/light_client/ClientDataModel.cpp +++ b/programs/light_client/ClientDataModel.cpp @@ -5,58 +5,32 @@ #include +#include + using namespace graphene::app; -ChainDataModel::ChainDataModel( fc::thread& t, QObject* parent ) -:QObject(parent),m_thread(&t){} +template +QString idToString(T id) { + return QString("%1.%2.%3").arg(T::space_id).arg(T::type_id).arg(ObjectId(id.instance)); +} +QString idToString(graphene::db::object_id_type id) { + return QString("%1.%2.%3").arg(id.space(), id.type(), ObjectId(id.instance())); +} +ChainDataModel::ChainDataModel(fc::thread& t, QObject* parent) +:QObject(parent),m_rpc_thread(&t){} Asset* ChainDataModel::getAsset(ObjectId id) { - auto& by_id_idx = m_assets.get<::by_id>(); + auto& by_id_idx = m_assets.get(); auto itr = by_id_idx.find(id); - if( itr == by_id_idx.end() ) + if (itr == by_id_idx.end()) { - auto tmp = new Asset(id, QString::number(--m_account_query_num), 0, this); - auto result = m_assets.insert( tmp ); - assert( result.second ); + auto result = m_assets.insert(new Asset(id, QString::number(--m_account_query_num), 0, this)); + assert(result.second); - /** execute in app thread */ - m_thread->async( [this,id](){ - try { - ilog( "look up symbol.." ); - auto result = m_db_api->get_assets( {asset_id_type(id)} ); - wdump((result)); - - /** execute in main */ - Q_EMIT queueExecute( [this,result,id](){ - wlog( "process result" ); - auto& by_id_idx = this->m_assets.get<::by_id>(); - auto itr = by_id_idx.find(id); - assert( itr != by_id_idx.end() ); - - if( result.size() == 0 || !result.front() ) - { - elog( "delete later" ); - (*itr)->deleteLater(); - by_id_idx.erase( itr ); - } - else - { - by_id_idx.modify( itr, - [=]( Asset* a ){ - a->setProperty("symbol", QString::fromStdString(result.front()->symbol) ); - a->setProperty("precision", result.front()->precision ); - } - ); - } - }); - } - catch ( const fc::exception& e ) - { - Q_EMIT exceptionThrown( QString::fromStdString(e.to_string()) ); - } - }); + // Run in RPC thread + m_rpc_thread->async([this,id,result]{ getAssetImpl(idToString(asset_id_type(id)), &*result.first); }); return *result.first; } return *itr; @@ -66,94 +40,138 @@ Asset* ChainDataModel::getAsset(QString symbol) { auto& by_symbol_idx = m_assets.get(); auto itr = by_symbol_idx.find(symbol); - if( itr == by_symbol_idx.end() ) + if (itr == by_symbol_idx.end()) { - auto tmp = new Asset(--m_account_query_num, symbol, 0, this); - auto result = m_assets.insert( tmp ); - assert( result.second ); + auto result = m_assets.insert(new Asset(--m_account_query_num, symbol, 0, this)); + assert(result.second); - /** execute in app thread */ - m_thread->async( [this,symbol](){ - try { - ilog( "look up symbol.." ); - auto result = m_db_api->lookup_asset_symbols( {symbol.toStdString()} ); - /** execute in main */ - Q_EMIT queueExecute( [this,result,symbol](){ - wlog( "process result" ); - auto& by_symbol_idx = this->m_assets.get(); - auto itr = by_symbol_idx.find(symbol); - assert( itr != by_symbol_idx.end() ); - - if( result.size() == 0 || !result.front() ) - { - elog( "delete later" ); - (*itr)->deleteLater(); - by_symbol_idx.erase( itr ); - } - else - { - by_symbol_idx.modify( itr, - [=]( Asset* a ){ - a->setProperty("id", ObjectId(result.front()->id.instance())); - a->setProperty("precision", result.front()->precision ); - } - ); - } - }); - } - catch ( const fc::exception& e ) - { - Q_EMIT exceptionThrown( QString::fromStdString(e.to_string()) ); - } - }); + // Run in RPC thread + m_rpc_thread->async([this,symbol,result](){ getAssetImpl(symbol, &*result.first); }); return *result.first; } return *itr; } +void ChainDataModel::getAssetImpl(QString assetIdentifier, Asset* const * assetInContainer) +{ + try { + ilog("Fetching asset ${asset}", ("asset", assetIdentifier.toStdString())); + auto result = m_db_api->lookup_asset_symbols({assetIdentifier.toStdString()}); + + // Run in main thread + Q_EMIT queueExecute([this,result,assetInContainer](){ + ilog("Processing result ${r}", ("r", result)); + auto itr = m_assets.iterator_to(*assetInContainer); + + if (result.size() == 0 || !result.front()) { + (*itr)->deleteLater(); + m_assets.erase(itr); + } else { + m_assets.modify(itr, + [=](Asset* a){ + a->setProperty("symbol", QString::fromStdString(result.front()->symbol)); + a->setProperty("id", ObjectId(result.front()->id.instance())); + a->setProperty("precision", result.front()->precision); + }); + } + }); + } + catch ( const fc::exception& e ) + { + Q_EMIT exceptionThrown(QString::fromStdString(e.to_string())); + } +} + +void ChainDataModel::getAccountImpl(QString accountIdentifier, Account* const * accountInContainer) +{ + try { + ilog("Fetching account ${acct}", ("acct", accountIdentifier.toStdString())); + auto result = m_db_api->get_full_accounts([](const fc::variant& v) { + idump((v)); + }, {accountIdentifier.toStdString()}); + fc::variant_object accountPackage; + + if (result.count(accountIdentifier.toStdString())) { + accountPackage = result.begin()->second.as(); + + // Fetch all necessary assets + auto balances = accountPackage["balances"].as>(); + QList assetsToFetch; + QList assetPlaceholders; + assetsToFetch.reserve(balances.size()); + // Get list of asset IDs the account has a balance in + std::transform(balances.begin(), balances.end(), std::back_inserter(assetsToFetch), + [](const account_balance_object& b) { return b.asset_type; }); + auto function = [this,&assetsToFetch,&assetPlaceholders] { + auto itr = assetsToFetch.begin(); + const auto& assets_by_id = m_assets.get(); + // Filter out assets I already have, create placeholders for the ones I don't. + while (itr != assetsToFetch.end()) { + if (assets_by_id.count(itr->instance)) + itr = assetsToFetch.erase(itr); + else { + assetPlaceholders.push_back(&*m_assets.insert(new Asset(itr->instance, QString(), 0, this)).first); + ++itr; + } + } + }; + QMetaObject::invokeMethod(parent(), "execute", Qt::BlockingQueuedConnection, + Q_ARG(const std::function&, function)); + assert(assetsToFetch.size() == assetPlaceholders.size()); + + // Blocking call to fetch and complete initialization for all the assets + for (int i = 0; i < assetsToFetch.size(); ++i) + getAssetImpl(idToString(assetsToFetch[i]), assetPlaceholders[i]); + } + + // Run in main thread + Q_EMIT queueExecute([this,accountPackage,accountInContainer](){ + ilog("Processing result ${r}", ("r", accountPackage)); + auto itr = m_accounts.iterator_to(*accountInContainer); + + if (!accountPackage.size()) { + (*itr)->deleteLater(); + m_accounts.erase(itr); + } else { + m_accounts.modify(itr, [=](Account* a){ + account_object account = accountPackage["account"].as(); + a->setProperty("id", ObjectId(account.id.instance())); + a->setProperty("name", QString::fromStdString(account.name)); + + // Set balances + QList balances; + auto balanceObjects = accountPackage["balances"].as>(); + std::transform(balanceObjects.begin(), balanceObjects.end(), std::back_inserter(balances), + [this](const account_balance_object& b) { + Balance* bal = new Balance; + bal->setParent(this); + bal->setProperty("amount", QVariant::fromValue(b.balance.value)); + bal->setProperty("type", QVariant::fromValue(getAsset(ObjectId(b.asset_type.instance)))); + return bal; + }); + a->setBalances(balances); + }); + } + }); + } + catch (const fc::exception& e) + { + Q_EMIT exceptionThrown(QString::fromStdString(e.to_string())); + } +} + Account* ChainDataModel::getAccount(ObjectId id) { - auto& by_id_idx = m_accounts.get<::by_id>(); + auto& by_id_idx = m_accounts.get(); auto itr = by_id_idx.find(id); if( itr == by_id_idx.end() ) { - auto tmp = new Account(id, QString::number(--m_account_query_num), this); - auto result = m_accounts.insert( tmp ); - assert( result.second ); + auto tmp = new Account(id, tr("Account #%1").arg(--m_account_query_num), this); + auto result = m_accounts.insert(tmp); + assert(result.second); - /** execute in app thread */ - m_thread->async( [this,id](){ - try { - ilog( "look up names.." ); - auto result = m_db_api->get_accounts( {account_id_type(id)} ); - /** execute in main */ - Q_EMIT queueExecute( [this,result,id](){ - wlog( "process result" ); - auto& by_id_idx = this->m_accounts.get<::by_id>(); - auto itr = by_id_idx.find(id); - assert( itr != by_id_idx.end() ); - - if( result.size() == 0 || !result.front() ) - { - elog( "delete later" ); - (*itr)->deleteLater(); - by_id_idx.erase( itr ); - } - else - { - by_id_idx.modify( itr, - [=]( Account* a ){ - a->setProperty("name", QString::fromStdString(result.front()->name) ); - } - ); - } - }); - } - catch ( const fc::exception& e ) - { - Q_EMIT exceptionThrown( QString::fromStdString(e.to_string()) ); - } - }); + // Run in RPC thread + m_rpc_thread->async([this, id, result]{getAccountImpl(idToString(account_id_type(id)), &*result.first);}); return *result.first; } return *itr; @@ -166,42 +184,11 @@ Account* ChainDataModel::getAccount(QString name) if( itr == by_name_idx.end() ) { auto tmp = new Account(--m_account_query_num, name, this); - auto result = m_accounts.insert( tmp ); - assert( result.second ); + auto result = m_accounts.insert(tmp); + assert(result.second); - /** execute in app thread */ - m_thread->async( [this,name](){ - try { - ilog( "look up names.." ); - auto result = m_db_api->lookup_account_names( {name.toStdString()} ); - /** execute in main */ - Q_EMIT queueExecute( [this,result,name](){ - wlog( "process result" ); - auto& by_name_idx = this->m_accounts.get(); - auto itr = by_name_idx.find(name); - assert( itr != by_name_idx.end() ); - - if( result.size() == 0 || !result.front() ) - { - elog( "delete later" ); - (*itr)->deleteLater(); - by_name_idx.erase( itr ); - } - else - { - by_name_idx.modify( itr, - [=]( Account* a ){ - a->setProperty("id", ObjectId(result.front()->id.instance())); - } - ); - } - }); - } - catch ( const fc::exception& e ) - { - Q_EMIT exceptionThrown( QString::fromStdString(e.to_string()) ); - } - }); + // Run in RPC thread + m_rpc_thread->async([this, name, result]{getAccountImpl(name, &*result.first);}); return *result.first; } return *itr; @@ -209,84 +196,78 @@ Account* ChainDataModel::getAccount(QString name) QQmlListProperty Account::balances() { - // This entire block is dummy data. Throw it away when this function gets a real implementation. - if (m_balances.empty()) - { - auto asset = new Asset(0, QStringLiteral("CORE"), 5, this); - m_balances.append(new Balance); - m_balances.back()->setProperty("amount", 5000000); - m_balances.back()->setProperty("type", QVariant::fromValue(asset)); - asset = new Asset(0, QStringLiteral("USD"), 2, this); - m_balances.append(new Balance); - m_balances.back()->setProperty("amount", 3099); - m_balances.back()->setProperty("type", QVariant::fromValue(asset)); - } + auto count = [](QQmlListProperty* list) { + return reinterpret_cast(list->data)->m_balances.size(); + }; + auto at = [](QQmlListProperty* list, int index) { + return reinterpret_cast(list->data)->m_balances[index]; + }; - return QQmlListProperty(this, m_balances); + return QQmlListProperty(this, this, count, at); } -GrapheneApplication::GrapheneApplication( QObject* parent ) -:QObject( parent ),m_thread("app") +GrapheneApplication::GrapheneApplication(QObject* parent) +:QObject(parent),m_thread("app") { - connect( this, &GrapheneApplication::queueExecute, - this, &GrapheneApplication::execute ); + connect(this, &GrapheneApplication::queueExecute, + this, &GrapheneApplication::execute); - m_model = new ChainDataModel( m_thread, this ); + m_model = new ChainDataModel(m_thread, this); - connect( m_model, &ChainDataModel::queueExecute, - this, &GrapheneApplication::execute ); + connect(m_model, &ChainDataModel::queueExecute, + this, &GrapheneApplication::execute); - connect( m_model, &ChainDataModel::exceptionThrown, - this, &GrapheneApplication::exceptionThrown ); + connect(m_model, &ChainDataModel::exceptionThrown, + this, &GrapheneApplication::exceptionThrown); } GrapheneApplication::~GrapheneApplication() { } -void GrapheneApplication::setIsConnected( bool v ) +void GrapheneApplication::setIsConnected(bool v) { - if( v != m_isConnected ) + if (v != m_isConnected) { m_isConnected = v; - Q_EMIT isConnectedChanged( m_isConnected ); + Q_EMIT isConnectedChanged(m_isConnected); } } -void GrapheneApplication::start( QString apiurl, QString user, QString pass ) +void GrapheneApplication::start(QString apiurl, QString user, QString pass) { - if( !m_thread.is_current() ) + if (!m_thread.is_current()) { - m_done = m_thread.async( [=](){ return start( apiurl, user, pass ); } ); + m_done = m_thread.async([=](){ return start(apiurl, user, pass); }); return; } try { m_client = std::make_shared(); - ilog( "connecting...${s}", ("s",apiurl.toStdString()) ); - auto con = m_client->connect( apiurl.toStdString() ); + ilog("connecting...${s}", ("s",apiurl.toStdString())); + auto con = m_client->connect(apiurl.toStdString()); m_connectionClosed = con->closed.connect([this]{queueExecute([this]{setIsConnected(false);});}); auto apic = std::make_shared(*con); - auto remote_api = apic->get_remote_api< login_api >(1); - auto db_api = apic->get_remote_api< database_api >(0); - if( !remote_api->login( user.toStdString(), pass.toStdString() ) ) + auto remote_api = apic->get_remote_api(1); + auto db_api = apic->get_remote_api(0); + if (!remote_api->login(user.toStdString(), pass.toStdString())) { - elog( "login failed" ); + elog("login failed"); Q_EMIT loginFailed(); return; } - ilog( "connecting..." ); - queueExecute( [=](){ - m_model->setDatabaseAPI( db_api ); + ilog("connecting..."); + queueExecute([=](){ + m_model->setDatabaseAPI(db_api); }); - queueExecute( [=](){setIsConnected( true );} ); - } catch ( const fc::exception& e ) + queueExecute([=](){ setIsConnected(true); }); + } catch (const fc::exception& e) { - Q_EMIT exceptionThrown( QString::fromStdString(e.to_string()) ); + Q_EMIT exceptionThrown(QString::fromStdString(e.to_string())); } } -Q_SLOT void GrapheneApplication::execute( const std::function& func )const +Q_SLOT void GrapheneApplication::execute(const std::function& func)const { func(); } diff --git a/programs/light_client/ClientDataModel.hpp b/programs/light_client/ClientDataModel.hpp index 1968a194..5f6c99da 100644 --- a/programs/light_client/ClientDataModel.hpp +++ b/programs/light_client/ClientDataModel.hpp @@ -18,6 +18,8 @@ using boost::multi_index_container; using namespace boost::multi_index; +using graphene::chain::by_id; + using ObjectId = qint64; Q_DECLARE_METATYPE(ObjectId) @@ -83,7 +85,6 @@ Q_SIGNALS: void precisionChanged(); }; -struct by_id; struct by_symbol_name; typedef multi_index_container< Asset*, @@ -130,6 +131,13 @@ public: QString name()const { return m_name; } QQmlListProperty balances(); + void setBalances(QList balances) { + if (balances != m_balances) { + m_balances = balances; + Q_EMIT balancesChanged(); + } + } + Q_SIGNALS: void nameChanged(); void balancesChanged(); @@ -147,6 +155,9 @@ typedef multi_index_container< class ChainDataModel : public QObject { Q_OBJECT + void getAssetImpl(QString assetIdentifier, Asset* const * assetInContainer); + void getAccountImpl(QString accountIdentifier, Account* const * accountInContainer); + public: Q_INVOKABLE Account* getAccount(ObjectId id); Q_INVOKABLE Account* getAccount(QString name); @@ -163,7 +174,7 @@ Q_SIGNALS: void exceptionThrown( QString message ); private: - fc::thread* m_thread = nullptr; + fc::thread* m_rpc_thread = nullptr; std::string m_api_url; fc::api m_db_api; @@ -186,13 +197,15 @@ class GrapheneApplication : public QObject { boost::signals2::scoped_connection m_connectionClosed; std::shared_ptr m_client; - fc::future m_done; + fc::future m_done; - void setIsConnected( bool v ); + void setIsConnected(bool v); + +protected Q_SLOTS: + void execute(const std::function&)const; - Q_SLOT void execute( const std::function& )const; public: - GrapheneApplication( QObject* parent = nullptr ); + GrapheneApplication(QObject* parent = nullptr); ~GrapheneApplication(); ChainDataModel* model() const @@ -201,8 +214,8 @@ public: } Q_INVOKABLE void start(QString apiUrl, - QString user, - QString pass ); + QString user, + QString pass); bool isConnected() const { diff --git a/programs/light_client/qml/FormBox.qml b/programs/light_client/qml/FormBox.qml index 69539789..653f40fe 100644 --- a/programs/light_client/qml/FormBox.qml +++ b/programs/light_client/qml/FormBox.qml @@ -84,7 +84,6 @@ Rectangle { StateChangeScript { name: "postHidden" script: { - console.log("Post") greySheet.closed() formContainer.data = [] if (internal.callback instanceof Function) diff --git a/programs/light_client/qml/main.qml b/programs/light_client/qml/main.qml index 03714d25..e6774b13 100644 --- a/programs/light_client/qml/main.qml +++ b/programs/light_client/qml/main.qml @@ -110,15 +110,10 @@ ApplicationWindow { if (acct == null) console.log("Got back null account") else if ( !(parseInt(acct.name) <= 0) ) - { - console.log("NAME ALREADY SET" ); console.log(JSON.stringify(acct)) - } - else - { + else { console.log("Waiting for result...") acct.nameChanged.connect(function() { - console.log( "NAME CHANGED" ); console.log(JSON.stringify(acct)) }) } @@ -141,14 +136,12 @@ ApplicationWindow { console.log("Got back null asset") else if ( !(parseInt(acct.name) <= 0) ) { - console.log("NAME ALREADY SET" ); console.log(JSON.stringify(acct)) } else { console.log("Waiting for result...") acct.nameChanged.connect(function() { - console.log( "NAME CHANGED" ); console.log(JSON.stringify(acct)) }) }