//SPDX-FileCopyrightText: 2023 Yury Gubich //SPDX-License-Identifier: GPL-3.0-or-later #include "magpie.h" #include #include "API/api.h" #include "API/codes.h" #include "utils/helpers.h" Models::Magpie::Magpie (QObject* parent): QObject(parent), currencies(), assets(currencies), address(), state(State::Offline), accessToken(), renewToken(), api(), firstPoll(), pollRequestId(API::none), requestingCurrencies(false) { firstPoll.setSingleShot(true); firstPoll.setInterval(2000); connect(&firstPoll, &QTimer::timeout, this, &Magpie::onPositivePoll); connect(&assets, &Assets::requestAssets, this, &Magpie::requestAssets); } QUrl Models::Magpie::getAddress () const { return address; } Models::Magpie::State Models::Magpie::getState () const { return state; } QString Models::Magpie::getAccessToken () const { return accessToken; } QString Models::Magpie::getRenewToken () const { return renewToken; } void Models::Magpie::installAPI (const std::shared_ptr& api) { Magpie::api = api; } void Models::Magpie::setAddress (const QUrl& address) { if (Magpie::address == address) return; if (state == Authenticated) { //do something } Magpie::address = address; emit addressChanged(address); if (address.isEmpty()) setState(NoServer); else setState(NotAuthenticated); } void Models::Magpie::forceAddress (const QUrl& address) { Magpie::address = ""; setAddress(address); } void Models::Magpie::setTokens (const QString access, const QString& renew, bool notify) { accessToken = access; renewToken = renew; setState(Authenticating); startPolling(); if (notify) emit storeTokens(accessToken, renewToken); } void Models::Magpie::setState (State newState) { if (newState == state) return; state = newState; emit stateChanged(state); } Models::Assets* Models::Magpie::getAssets () { return &assets; } Models::Currencies* Models::Magpie::getCurrencies () { return ¤cies; } void Models::Magpie::requestAssets () { api->requestAssets( [this] (const QVariantList& list) { std::deque result; bool res = Assets::deserialize(list, result); if (!res) { qDebug() << "Error deserializer received assets"; result.clear(); } else { qDebug() << "Assets successfully received"; } assets.add(result); }, [this] (const QString& error, const std::optional& data) { qDebug() << "Error receiving assets:" << error; } ); } void Models::Magpie::onPositivePoll () { if (state == Authenticating) requestCurrencies(); } void Models::Magpie::startPolling () { qDebug() << "Starting polling..."; if (state != Authenticating) throw std::runtime_error("Can not start polling in this state: " + std::to_string(state)); sendPoll(); firstPoll.start(); } void Models::Magpie::sendPoll (bool clear) { if (pollRequestId != API::none) throw std::runtime_error("an attempt to send second poll request while the other one is active"); pollRequestId = api->poll( std::bind(&Magpie::onPollSuccess, this, std::placeholders::_1), std::bind(&Magpie::onPollError, this, std::placeholders::_1, std::placeholders::_2), clear ); } void Models::Magpie::onPollSuccess (const QVariantMap& data) { pollRequestId = API::none; firstPoll.stop(); Codes::Poll code = Codes::convertPoll(data.value("result").toInt()); bool clear = false; switch (code) { case Codes::Poll::success: clear = handleChanges(qast(data.value("data"))); //todo handle the result case Codes::Poll::timeout: onPositivePoll(); if (state == Authenticating || state == Authenticated) return sendPoll(clear); case Codes::Poll::tokenProblem: case Codes::Poll::replace: case Codes::Poll::unknownError: //todo this one doesn't actually mean that we can't work for now, the network may be temporarily down or something return setState(NotAuthenticated); } } void Models::Magpie::onPollError (const QString& err, const std::optional& data) { qDebug() << "Poll error:" << err; pollRequestId = API::none; firstPoll.stop(); setState(NotAuthenticated); } bool Models::Magpie::handleChanges (const QVariantMap& changes) { QVariantMap::ConstIterator itr = changes.constFind("system"); if (itr != changes.constEnd() && itr.value().canConvert()) { const QVariantMap& sys = qast(itr.value()); QVariantMap::ConstIterator invItr = sys.constFind("invalidate"); if (invItr != sys.constEnd()) { const QVariant& vinv = invItr.value(); if (vinv.canConvert() && vinv.toBool()) resetAllModels(); } } bool result = true; itr = changes.constFind("assets"); if (itr != changes.constEnd() && itr.value().canConvert()) result = handleAssetChanges(qast(itr.value())); itr = changes.constFind("currencies"); if (itr != changes.constEnd() && itr.value().canConvert()) result = result && handleCurrenciesChanges(qast(itr.value())); return result; } bool Models::Magpie::handleAssetChanges (const QVariantMap& changes) { QVariantMap::ConstIterator aItr = changes.constFind("invalidate"); if (aItr != changes.constEnd()) { const QVariant& vinv = aItr.value(); if (vinv.canConvert() && vinv.toBool()) assets.clear(); } aItr = changes.constFind("added"); if (aItr != changes.constEnd() && aItr.value().canConvert()) { std::deque added; if (!Models::Assets::deserialize(qast(aItr.value()), added)) qDebug() << "Error deserializng added assets"; else assets.add(added); } aItr = changes.constFind("removed"); if (aItr != changes.constEnd() && aItr.value().canConvert()) { const QVariantList rem = qast(aItr.value()); for (const QVariant& vId : rem) { if (vId.isValid() && vId.canConvert()) assets.remove(vId.toUInt()); else qDebug() << "Error deserializing removed assets"; } } aItr = changes.constFind("changed"); if (aItr != changes.constEnd() && aItr.value().canConvert()) { std::deque changes; if (!Models::Assets::deserialize(qast(aItr.value()), changes)) qDebug() << "Error deserializng changed assets"; else assets.change(changes); } return true; } bool Models::Magpie::handleCurrenciesChanges (const QVariantMap& changes) { QVariantMap::ConstIterator aItr = changes.constFind("invalidate"); if (aItr != changes.constEnd()) { const QVariant& vinv = aItr.value(); if (vinv.canConvert() && vinv.toBool()) requestCurrencies(); } return true; } void Models::Magpie::resetAllModels () { assets.clear(); requestCurrencies(); } void Models::Magpie::requestCurrencies () { if (requestingCurrencies) return; requestingCurrencies = true; currencies.clear(); api->requestCurrencies( [this] (const QVariantList& list) { std::deque result; bool res = Currencies::deserialize(list, result); if (!res) { qDebug() << "Error deserializer received currencies"; result.clear(); } else { qDebug() << "Currencies successfully received"; } currencies.add(result); requestingCurrencies = false; setState(Authenticated); }, [this] (const QString& error, const std::optional& data) { qDebug() << "Error receiving currencies:" << error; requestingCurrencies = false; setState(NotAuthenticated); } ); }