QJSValue DecisionGeneratorApi::getDecisions(int count) { if (count > std::numeric_limits<uint8_t>::max()) // Fail hard for this; the GUI should guarantee count is valid qFatal("Application bug: count must fit in a uint8_t"); KJ_LOG(DBG, "Requesting decisions", count); auto request = generator.getValuesRequest(); request.setCount(count); m_isFetchingDecisions = true; emit this->isFetchingDecisionsChanged(true); auto pass = [this](auto&& e) { KJ_DBG(e); m_isFetchingDecisions = false; emit this->isFetchingDecisionsChanged(false); return kj::mv(e); }; auto contestsPromise = request.send().then([this, count](capnp::Response<DecisionGenerator::GetValuesResults> r) { if (!r.hasValues() || r.getValues().size() < count) { m_isOutOfDecisions = true; emit this->isOutOfDecisionsChanged(true); } m_isFetchingDecisions = false; emit this->isFetchingDecisionsChanged(false); return kj::mv(r); }, pass); return converter.convert(kj::mv(contestsPromise), [](capnp::Response<DecisionGenerator::GetValuesResults> r) -> QVariant { KJ_LOG(DBG, "Got decisions", r.getValues().size()); QVariantList decisions; for (auto decisionWrapper : r.getValues()) { auto decision = decisionWrapper.getValue(); decisions.append( QVariantMap{ {"decisionId", QString(convertBlob(ReaderPacker(decision.getId()).array()).toHex())}, {"counted", decision.getCounted()} }); } return QVariant(decisions); }); }
QJSValue ContestGeneratorApi::getContests(int count) { KJ_LOG(DBG, "Requesting contests", count); auto request = generator.getValuesRequest(); request.setCount(count); m_isFetchingContests = true; emit this->isFetchingContestsChanged(true); auto pass = [this](auto&& e) { m_isFetchingContests = false; emit this->isFetchingContestsChanged(false); return kj::mv(e); }; auto contestsPromise = request.send().then([this, count](capnp::Response<ContestGenerator::GetValuesResults> r) { if (!r.hasValues() || r.getValues().size() < count) { m_isOutOfContests = true; emit this->isOutOfContestsChanged(true); } m_isFetchingContests = false; emit this->isFetchingContestsChanged(false); return kj::mv(r); }, pass); return converter.convert(kj::mv(contestsPromise), [this](capnp::Response<ContestGenerator::GetValuesResults> r) -> QVariant { KJ_LOG(DBG, "Got contests", r.getValues().size()); QVariantList contests; for (auto contestWrapper : r.getValues()) { auto contest = contestWrapper.getValue(); auto results = new ContestResultsApi(contest.getContestResults(), converter); QQmlEngine::setObjectOwnership(results, QQmlEngine::JavaScriptOwnership); // TODO: Set engagement notification API (when server defines it) contests.append( QVariantMap{ {"contestId", QString(convertBlob(ReaderPacker(contest.getContestId()).array()).toHex())}, {"votingStake", qint64(contest.getVotingStake())}, {"resultsApi", QVariant::fromValue(results)} }); } return {QVariant(contests)}; }); }
kj::Promise<OwningWrapper<DecisionWrapper>*> ChainAdaptorWrapper::_getDecision(QString owner, QString contestId) { if (!hasAdaptor()) return KJ_EXCEPTION(FAILED, "No blockchain adaptor is set."); using Reader = ::Balance::Reader; auto promise = m_adaptor->getContest(QByteArray::fromHex(contestId.toLocal8Bit())).then([=](::Contest::Reader c) { return m_adaptor->getBalancesForOwner(owner).then([c](kj::Array<Reader> balances) { return std::make_tuple(c, kj::mv(balances)); }); }).then([=](std::tuple<::Contest::Reader, kj::Array<Reader>> contestAndBalances) { ::Contest::Reader contest; kj::Array<Reader> balances; std::tie(contest, balances) = kj::mv(contestAndBalances); auto newEnd = std::remove_if(balances.begin(), balances.end(), [contest](Reader balance) { return balance.getType() != contest.getContest().getCoin(); }); KJ_REQUIRE(newEnd - balances.begin() > 0, "No balances found in the contest's coin, so no decision exists."); // This struct is basically a lambda on steroids. I'm using it to transform the array of balances into an array // of datagrams containing the decision I want on each of those balances. Also, make sure the newest balance's // decision is in the front (I don't care about preserving order) so that the newest decision will be returned. struct { // Capture this ChainAdaptorWrapper* wrapper; // Capture contestId QString contestId; // For each balance, look up the datagram containing the relevant decision. Store the promises in this array kj::ArrayBuilder<kj::Promise<kj::Maybe<::Datagram::Reader>>> datagramPromises; Reader newestBalance; void operator() (Reader balance) { if (datagramPromises.size() == 0) { // First balance. Nothing to compare. newestBalance = balance; } // Start a lookup for the datagram and store the promise. auto promise = wrapper->m_adaptor->getDatagram(convertBlob(balance.getId()), Datagram::DatagramType::DECISION, contestId); datagramPromises.add(promise.then([](::Datagram::Reader r) -> kj::Maybe<::Datagram::Reader> { return r; }, [](kj::Exception e) -> kj::Maybe<::Datagram::Reader> { qInfo() << "Got exception when fetching datagram on balance:" << e.getDescription().cStr(); return {}; }) ); // If this balance is newer than the previous newest, move its promise to the front if (balance.getCreationOrder() > newestBalance.getCreationOrder()) { newestBalance = balance; std::swap(datagramPromises.front(), datagramPromises.back()); } } } accumulator{this, contestId, kj::heapArrayBuilder<kj::Promise<kj::Maybe<::Datagram::Reader>>>(newEnd - balances.begin()), {}}; // Process all of the balances. Fetch the appropriate decision for each. qDebug() << "Looking for decisions on" << newEnd - balances.begin() << "balances."; accumulator = std::for_each(balances.begin(), newEnd, kj::mv(accumulator)); return kj::joinPromises(accumulator.datagramPromises.finish()); }).then([=](kj::Array<kj::Maybe<::Datagram::Reader>> datagrams) { std::unique_ptr<OwningWrapper<swv::DecisionWrapper>> decision; for (auto datagramMaybe : datagrams) { KJ_IF_MAYBE(datagram, datagramMaybe) { decision.reset(OwningWrapper<DecisionWrapper>::deserialize(convertBlob(datagram->getContent()))); break; } }