some first thoguhts of receiving actual data
This commit is contained in:
parent
74701e9431
commit
1597bd9522
@ -5,12 +5,16 @@ set(HEADERS
|
|||||||
api.h
|
api.h
|
||||||
codes.h
|
codes.h
|
||||||
finalaction.h
|
finalaction.h
|
||||||
|
helpers.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
api.cpp
|
api.cpp
|
||||||
codes.cpp
|
codes.cpp
|
||||||
finalaction.cpp
|
finalaction.cpp
|
||||||
|
helpers.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(magpie PRIVATE ${SOURCES})
|
target_sources(magpie PRIVATE ${SOURCES})
|
||||||
|
|
||||||
|
add_subdirectory(models)
|
||||||
|
230
API/api.cpp
230
API/api.cpp
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "codes.h"
|
#include "codes.h"
|
||||||
#include "finalaction.h"
|
#include "finalaction.h"
|
||||||
|
#include "helpers.h"
|
||||||
|
|
||||||
constexpr const char* json = "application/json";
|
constexpr const char* json = "application/json";
|
||||||
constexpr const char* urlEncoded = "application/x-www-form-urlencoded";
|
constexpr const char* urlEncoded = "application/x-www-form-urlencoded";
|
||||||
@ -41,11 +42,13 @@ API::API(const QUrl& address, QObject* parent):
|
|||||||
accessToken(),
|
accessToken(),
|
||||||
renewToken(),
|
renewToken(),
|
||||||
firstPoll(),
|
firstPoll(),
|
||||||
pollReply()
|
pollReply(),
|
||||||
|
assets()
|
||||||
{
|
{
|
||||||
firstPoll.setSingleShot(true);
|
firstPoll.setSingleShot(true);
|
||||||
firstPoll.setInterval(2000);
|
firstPoll.setInterval(2000);
|
||||||
connect(&firstPoll, &QTimer::timeout, this, &API::onFirstPollSuccess);
|
connect(&firstPoll, &QTimer::timeout, this, &API::onFirstPollSuccess);
|
||||||
|
connect(&assets, &Models::Assets::requestAssets, this, &API::requestAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl API::getAddress() const {
|
QUrl API::getAddress() const {
|
||||||
@ -134,24 +137,6 @@ QUrl API::createUrl(const QString &path) const {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
void API::sendPoll() {
|
|
||||||
if (accessToken.isEmpty())
|
|
||||||
throw std::runtime_error("Can not start polling: access token is empty");
|
|
||||||
|
|
||||||
QByteArray authorizationHeader = "Bearer " + accessToken.toUtf8();
|
|
||||||
QNetworkRequest request(createUrl("/poll"));
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, json);
|
|
||||||
request.setRawHeader("Authorization", authorizationHeader);
|
|
||||||
request.setTransferTimeout(30000);
|
|
||||||
|
|
||||||
pollReply = std::unique_ptr<QNetworkReply>(network.get(request));
|
|
||||||
connect(
|
|
||||||
pollReply.get(), &QNetworkReply::finished,
|
|
||||||
this, &API::onPollFinished,
|
|
||||||
Qt::QueuedConnection
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void API::test(const QString& path, const QJSValue& finished) {
|
void API::test(const QString& path, const QJSValue& finished) {
|
||||||
qDebug() << "Testing" << path;
|
qDebug() << "Testing" << path;
|
||||||
if (state == Offline)
|
if (state == Offline)
|
||||||
@ -209,25 +194,6 @@ void API::sendRegister(const QString& login, const QString& password, const QJSV
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void API::sendLogin(const QString& login, const QString& password, const QJSValue& finished) {
|
|
||||||
qDebug() << "Logging in...";
|
|
||||||
if (state != NotAuthenticated)
|
|
||||||
return callCallback(finished, "Can not register in current state");
|
|
||||||
|
|
||||||
QUrlQuery params({
|
|
||||||
{"login", login},
|
|
||||||
{"password", password}
|
|
||||||
});
|
|
||||||
|
|
||||||
QNetworkRequest request(createUrl("/login"));
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, urlEncoded);
|
|
||||||
setState(Authenticating);
|
|
||||||
QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8());
|
|
||||||
connect(reply, &QNetworkReply::finished,
|
|
||||||
std::bind(&API::onLoginFinished, this, reply, finished)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void API::onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const {
|
void API::onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const {
|
||||||
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
|
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
|
||||||
QNetworkReply::NetworkError error = reply->error();
|
QNetworkReply::NetworkError error = reply->error();
|
||||||
@ -251,6 +217,25 @@ void API::onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) con
|
|||||||
callCallback(finished, detail, {QJSValue(success)});
|
callCallback(finished, detail, {QJSValue(success)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void API::sendLogin(const QString& login, const QString& password, const QJSValue& finished) {
|
||||||
|
qDebug() << "Logging in...";
|
||||||
|
if (state != NotAuthenticated)
|
||||||
|
return callCallback(finished, "Can not register in current state");
|
||||||
|
|
||||||
|
QUrlQuery params({
|
||||||
|
{"login", login},
|
||||||
|
{"password", password}
|
||||||
|
});
|
||||||
|
|
||||||
|
QNetworkRequest request(createUrl("/login"));
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, urlEncoded);
|
||||||
|
setState(Authenticating);
|
||||||
|
QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8());
|
||||||
|
connect(reply, &QNetworkReply::finished,
|
||||||
|
std::bind(&API::onLoginFinished, this, reply, finished)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) {
|
void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) {
|
||||||
State state = NotAuthenticated;
|
State state = NotAuthenticated;
|
||||||
FinalAction action([this, &state]() { //this should be executed on leaving the function in any way
|
FinalAction action([this, &state]() { //this should be executed on leaving the function in any way
|
||||||
@ -289,6 +274,57 @@ void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) {
|
|||||||
startPolling();
|
startPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void API::addAsset(const QString& title, const QString& icon, const QJSValue &finished) {
|
||||||
|
qDebug() << "Adding asset...";
|
||||||
|
if (state != Authenticated)
|
||||||
|
return callCallback(finished, "Can not add assets in current state");
|
||||||
|
|
||||||
|
QUrlQuery params({
|
||||||
|
{"title", title},
|
||||||
|
{"icon", icon}
|
||||||
|
});
|
||||||
|
|
||||||
|
QNetworkRequest request(createUrl("/addAsset"));
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, urlEncoded);
|
||||||
|
QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8());
|
||||||
|
connect(reply, &QNetworkReply::finished,
|
||||||
|
std::bind(&API::onAssetAdded, this, reply, finished)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void API::onAssetAdded(QNetworkReply *reply, const QJSValue &finished) {
|
||||||
|
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
|
||||||
|
QNetworkReply::NetworkError error = reply->error();
|
||||||
|
std::optional<QVariantMap> data = readResult(reply);
|
||||||
|
|
||||||
|
if (error != QNetworkReply::NoError)
|
||||||
|
return callCallback(finished, reply->errorString());
|
||||||
|
|
||||||
|
callCallback(finished, QString(), {QJSValue(true)});
|
||||||
|
}
|
||||||
|
|
||||||
|
void API::sendPoll(bool clear) {
|
||||||
|
if (accessToken.isEmpty())
|
||||||
|
throw std::runtime_error("Can not start polling: access token is empty");
|
||||||
|
|
||||||
|
QUrl url = createUrl("/poll");
|
||||||
|
if (clear)
|
||||||
|
url.setQuery(QUrlQuery({{"clearCache", "all"}}));
|
||||||
|
|
||||||
|
QByteArray authorizationHeader = "Bearer " + accessToken.toUtf8();
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, json);
|
||||||
|
request.setRawHeader("Authorization", authorizationHeader);
|
||||||
|
request.setTransferTimeout(30000);
|
||||||
|
|
||||||
|
pollReply = std::unique_ptr<QNetworkReply>(network.get(request));
|
||||||
|
connect(
|
||||||
|
pollReply.get(), &QNetworkReply::finished,
|
||||||
|
this, &API::onPollFinished,
|
||||||
|
Qt::QueuedConnection
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void API::onPollFinished() {
|
void API::onPollFinished() {
|
||||||
firstPoll.stop();
|
firstPoll.stop();
|
||||||
QNetworkReply::NetworkError error = pollReply->error();
|
QNetworkReply::NetworkError error = pollReply->error();
|
||||||
@ -304,11 +340,23 @@ void API::onPollFinished() {
|
|||||||
else
|
else
|
||||||
qDebug() << "Poll finished: " + detail;
|
qDebug() << "Poll finished: " + detail;
|
||||||
|
|
||||||
|
bool clear = false;
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case Codes::Poll::success:
|
case Codes::Poll::success: {
|
||||||
|
QVariantMap::ConstIterator itr = data->constFind("data");
|
||||||
|
if (itr == data->constEnd())
|
||||||
|
qDebug("received a poll that claimed to have some updates, but the \"data\" field is abscent");
|
||||||
|
|
||||||
|
const QVariant& vdata = itr.value();
|
||||||
|
if (vdata.canConvert<QVariantMap>())
|
||||||
|
clear = handleChanges(qast<QVariantMap>(itr.value()));
|
||||||
|
else
|
||||||
|
qDebug("received a poll that claimed to have some updates, but the \"data\" field is not an object");
|
||||||
|
}
|
||||||
//todo handle the result
|
//todo handle the result
|
||||||
case Codes::Poll::timeout:
|
case Codes::Poll::timeout:
|
||||||
return sendPoll();
|
setState(Authenticated);
|
||||||
|
return sendPoll(clear);
|
||||||
case Codes::Poll::tokenProblem:
|
case Codes::Poll::tokenProblem:
|
||||||
case Codes::Poll::replace:
|
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
|
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
|
||||||
@ -317,10 +365,112 @@ void API::onPollFinished() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool API::handleChanges(const QVariantMap& changes) {
|
||||||
|
QVariantMap::ConstIterator itr = changes.constFind("system");
|
||||||
|
if (itr != changes.constEnd()) {
|
||||||
|
const QVariant& vsys = itr.value();
|
||||||
|
if (vsys.canConvert<QVariantMap>()) {
|
||||||
|
const QVariantMap& sys = qast<QVariantMap>(vsys);
|
||||||
|
QVariantMap::ConstIterator invItr = sys.constFind("invalidate");
|
||||||
|
if (invItr != sys.constEnd()) {
|
||||||
|
const QVariant& vinv = invItr.value();
|
||||||
|
if (vinv.canConvert<bool>() && vinv.toBool())
|
||||||
|
resetAllModels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itr = changes.constFind("assets");
|
||||||
|
if (itr != changes.constEnd()) {
|
||||||
|
const QVariant& vassets = itr.value();
|
||||||
|
if (vassets.canConvert<QVariantMap>()) {
|
||||||
|
const QVariantMap& assets = qast<QVariantMap>(vassets);
|
||||||
|
QVariantMap::ConstIterator aItr = assets.constFind("invalidate");
|
||||||
|
if (aItr != assets.constEnd()) {
|
||||||
|
const QVariant& vinv = aItr.value();
|
||||||
|
if (vinv.canConvert<bool>() && vinv.toBool())
|
||||||
|
API::assets.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
aItr = assets.constFind("added");
|
||||||
|
if (aItr != assets.constEnd()) {
|
||||||
|
const QVariant& vadd = aItr.value();
|
||||||
|
std::deque<Models::Asset> added;
|
||||||
|
if (!Models::Assets::deserialize(qast<QVariantList>(itr.value()), added))
|
||||||
|
qDebug() << "Error deserializng added assets";
|
||||||
|
else
|
||||||
|
API::assets.addAssets(added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void API::resetAllModels() {
|
||||||
|
assets.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void API::onFirstPollSuccess() {
|
void API::onFirstPollSuccess() {
|
||||||
setState(Authenticated);
|
setState(Authenticated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void API::requestAssets() {
|
||||||
|
if (state != Authenticated) {
|
||||||
|
qDebug() << "An attempt to request assets on unauthenticated state";
|
||||||
|
assets.receivedAssets({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug() << "Requesting assets...";
|
||||||
|
|
||||||
|
QUrl url = createUrl("/listAssets");
|
||||||
|
QByteArray authorizationHeader = "Bearer " + accessToken.toUtf8();
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, json);
|
||||||
|
request.setRawHeader("Authorization", authorizationHeader);
|
||||||
|
|
||||||
|
QNetworkReply* reply = network.get(request);
|
||||||
|
connect(
|
||||||
|
pollReply.get(), &QNetworkReply::finished,
|
||||||
|
this, std::bind(&API::responseAssets, this, reply)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void API::responseAssets(QNetworkReply *reply) {
|
||||||
|
std::deque<Models::Asset> result;
|
||||||
|
FinalAction action([this, &result]() {
|
||||||
|
assets.receivedAssets(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
|
||||||
|
QNetworkReply::NetworkError error = reply->error();
|
||||||
|
|
||||||
|
if (error != QNetworkReply::NoError) {
|
||||||
|
qDebug() << "Error receiving assets:" << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QVariantMap> data = readResult(reply);
|
||||||
|
if (!data) {
|
||||||
|
qDebug() << "Error receiving assets: bad data";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap::ConstIterator itr = data->find("assets");
|
||||||
|
if (itr == data->end() || !itr->canConvert<QVariantList>()) {
|
||||||
|
qDebug() << "Error receiving assets: assets are missing or not in an array";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Models::Assets::deserialize(qast<QVariantList>(itr.value()), result)) {
|
||||||
|
qDebug() << "Error deserializng assets";
|
||||||
|
result.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Assets successfully received";
|
||||||
|
//final action goes automatically
|
||||||
|
}
|
||||||
|
|
||||||
void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const {
|
void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const {
|
||||||
if (callback.isCallable()) {
|
if (callback.isCallable()) {
|
||||||
if (error.isEmpty())
|
if (error.isEmpty())
|
||||||
|
14
API/api.h
14
API/api.h
@ -15,6 +15,8 @@
|
|||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "models/assets.h"
|
||||||
|
|
||||||
class Root;
|
class Root;
|
||||||
class API : public QObject {
|
class API : public QObject {
|
||||||
friend class Root;
|
friend class Root;
|
||||||
@ -51,15 +53,20 @@ public slots:
|
|||||||
void test(const QString& path, const QJSValue& finished = QJSValue());
|
void test(const QString& path, const QJSValue& finished = QJSValue());
|
||||||
void sendRegister(const QString& login, const QString& password, const QJSValue& finished = QJSValue());
|
void sendRegister(const QString& login, const QString& password, const QJSValue& finished = QJSValue());
|
||||||
void sendLogin(const QString& login, const QString& password, const QJSValue& finished = QJSValue());
|
void sendLogin(const QString& login, const QString& password, const QJSValue& finished = QJSValue());
|
||||||
|
void addAsset(const QString& title, const QString& icon, const QJSValue& finished = QJSValue());
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onTestFinished(QNetworkReply* reply, const QUrl& addr, const QJSValue& finished);
|
void onTestFinished(QNetworkReply* reply, const QUrl& addr, const QJSValue& finished);
|
||||||
void onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const;
|
void onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const;
|
||||||
void onLoginFinished(QNetworkReply* reply, const QJSValue& finished);
|
void onLoginFinished(QNetworkReply* reply, const QJSValue& finished);
|
||||||
|
void onAssetAdded(QNetworkReply* reply, const QJSValue& finished);
|
||||||
void onPollFinished();
|
void onPollFinished();
|
||||||
|
|
||||||
void onFirstPollSuccess();
|
void onFirstPollSuccess();
|
||||||
|
|
||||||
|
void requestAssets();
|
||||||
|
void responseAssets(QNetworkReply* reply);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void callCallback(const QJSValue& callback, const QString& error = QString(), const QJSValueList& arguments = QJSValueList()) const;
|
void callCallback(const QJSValue& callback, const QString& error = QString(), const QJSValueList& arguments = QJSValueList()) const;
|
||||||
void setAddress(const QUrl& path);
|
void setAddress(const QUrl& path);
|
||||||
@ -67,7 +74,9 @@ private:
|
|||||||
static bool validateResponse(const std::optional<QVariantMap>& data, const std::map<QString, QMetaType::Type>& structure);
|
static bool validateResponse(const std::optional<QVariantMap>& data, const std::map<QString, QMetaType::Type>& structure);
|
||||||
void setState(State newState);
|
void setState(State newState);
|
||||||
QUrl createUrl(const QString& path) const;
|
QUrl createUrl(const QString& path) const;
|
||||||
void sendPoll();
|
void sendPoll(bool clear = false);
|
||||||
|
bool handleChanges(const QVariantMap& changes);
|
||||||
|
void resetAllModels();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUrl address;
|
QUrl address;
|
||||||
@ -77,4 +86,7 @@ private:
|
|||||||
QString renewToken;
|
QString renewToken;
|
||||||
QTimer firstPoll;
|
QTimer firstPoll;
|
||||||
std::unique_ptr<QNetworkReply> pollReply;
|
std::unique_ptr<QNetworkReply> pollReply;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Models::Assets assets;
|
||||||
};
|
};
|
||||||
|
5
API/helpers.cpp
Normal file
5
API/helpers.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
|
||||||
|
//SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "helpers.h"
|
||||||
|
|
15
API/helpers.h
Normal file
15
API/helpers.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
|
||||||
|
//SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QVariant>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
const T& qast(const QVariant& variant) {
|
||||||
|
if (variant.userType() == qMetaTypeId<T>())
|
||||||
|
return *reinterpret_cast<const T*>(variant.data());
|
||||||
|
|
||||||
|
throw std::runtime_error("An usuccessfull qast");
|
||||||
|
}
|
12
API/models/CMakeLists.txt
Normal file
12
API/models/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
set(HEADERS
|
||||||
|
assets.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
assets.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(magpie PRIVATE ${SOURCES})
|
138
API/models/assets.cpp
Normal file
138
API/models/assets.cpp
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
//SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
|
||||||
|
//SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "assets.h"
|
||||||
|
|
||||||
|
#include "../helpers.h"
|
||||||
|
|
||||||
|
Models::Assets::Assets (QObject* parent):
|
||||||
|
QAbstractListModel(parent),
|
||||||
|
records(),
|
||||||
|
state(State::initial)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Models::Assets::clear () {
|
||||||
|
beginResetModel();
|
||||||
|
records.clear();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Assets::addAsset (const Asset& asset) {
|
||||||
|
QModelIndex index = getIndex(asset.id);
|
||||||
|
if (index.isValid())
|
||||||
|
throw std::runtime_error("An attempt to insert a duplicating Asset to an asset model");
|
||||||
|
|
||||||
|
beginInsertRows(QModelIndex(), records.size(), records.size() + 1);
|
||||||
|
records.push_back(asset);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Assets::addAssets (const std::deque<Asset>& assets) {
|
||||||
|
if (assets.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const Asset& asset : assets)
|
||||||
|
if (getIndex(asset.id).isValid())
|
||||||
|
throw std::runtime_error("An attempt to insert a duplicating Asset to an asset model (bulk)");
|
||||||
|
|
||||||
|
beginInsertRows(QModelIndex(), records.size(), records.size() + assets.size());
|
||||||
|
for (const Asset& asset : assets)
|
||||||
|
records.push_back(asset);
|
||||||
|
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Assets::deleteAsset (unsigned int id) {
|
||||||
|
QModelIndex index = getIndex(id);
|
||||||
|
if (!index.isValid())
|
||||||
|
throw std::runtime_error("An attempt to insert to delete non existing Asset from asset model");
|
||||||
|
|
||||||
|
int row = index.row();
|
||||||
|
beginRemoveRows(QModelIndex(), row, row + 1);
|
||||||
|
records.erase(records.begin() + row);
|
||||||
|
if (state == State::syncronized) //give a second thought
|
||||||
|
state = State::initial;
|
||||||
|
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Models::Assets::rowCount (const QModelIndex& parent) const {
|
||||||
|
//For list models only the root node (an invalid parent) should return the
|
||||||
|
//list's size. For all other (valid) parents, rowCount() should return 0 so
|
||||||
|
//that it does not become a tree model.
|
||||||
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return records.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> Models::Assets::roleNames () const {
|
||||||
|
static const QHash<int, QByteArray> roleNames{
|
||||||
|
{Title, "title"}, {Icon, "icon"}, {Balance, "balance"}, {Archived, "archived"}
|
||||||
|
};
|
||||||
|
return roleNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Models::Assets::canFetchMore (const QModelIndex& parent) const {
|
||||||
|
return state == State::initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Assets::fetchMore (const QModelIndex& parent) {
|
||||||
|
if (state != State::initial)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state = State::requesting;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant Models::Assets::data (const QModelIndex& index, int role) const {
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
int row = index.row();
|
||||||
|
if (row >= 0 && row < records.size()) {
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
case Title:
|
||||||
|
return records[row].title;
|
||||||
|
case Icon:
|
||||||
|
return records[row].icon;
|
||||||
|
case Balance:
|
||||||
|
return records[row].balance;
|
||||||
|
case Archived:
|
||||||
|
return records[row].archived;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Models::Assets::deserialize (const QVariantList& from, std::deque<Asset>& out) {
|
||||||
|
for (const QVariant& item : from) {
|
||||||
|
if (!item.canConvert<QVariantMap>())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const QVariantMap& ser = qast<QVariantMap>(item);
|
||||||
|
Asset& asset = out.emplace_back();
|
||||||
|
asset.title = ser.value("title").toString();
|
||||||
|
asset.icon = ser.value("icon").toString();
|
||||||
|
asset.archived = ser.value("archived").toBool();
|
||||||
|
asset.id = ser.value("archived").toUInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Assets::receivedAssets (const std::deque<Asset>& assets) {
|
||||||
|
beginResetModel();
|
||||||
|
records = assets;
|
||||||
|
state = State::syncronized;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex Models::Assets::getIndex (unsigned int id) const {
|
||||||
|
for (std::size_t i = 0; i < records.size(); ++i) {
|
||||||
|
if (records[i].id == id)
|
||||||
|
return createIndex(i, 0, &records[i]);
|
||||||
|
}
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
71
API/models/assets.h
Normal file
71
API/models/assets.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
//SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
|
||||||
|
//SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <qqmlregistration.h>
|
||||||
|
|
||||||
|
namespace Models {
|
||||||
|
struct Asset {
|
||||||
|
unsigned int id;
|
||||||
|
QString title;
|
||||||
|
QString icon;
|
||||||
|
double balance;
|
||||||
|
bool archived;
|
||||||
|
unsigned int currency;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Assets : public QAbstractListModel {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Assets (QObject* parent = nullptr);
|
||||||
|
|
||||||
|
enum Roles {
|
||||||
|
Title = Qt::UserRole + 1,
|
||||||
|
Icon,
|
||||||
|
Balance,
|
||||||
|
Archived
|
||||||
|
};
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void addAsset(const Asset& asset);
|
||||||
|
void addAssets(const std::deque<Asset>& assets);
|
||||||
|
void deleteAsset(unsigned int id);
|
||||||
|
|
||||||
|
//Basic functionality:
|
||||||
|
int rowCount (const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
QHash<int, QByteArray> roleNames () const override;
|
||||||
|
|
||||||
|
//Fetch data dynamically:
|
||||||
|
bool canFetchMore (const QModelIndex& parent) const override;
|
||||||
|
void fetchMore (const QModelIndex& parent) override;
|
||||||
|
QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
static bool deserialize(const QVariantList& from, std::deque<Asset>& out);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void requestAssets();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void receivedAssets(const std::deque<Asset>& assets);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QModelIndex getIndex(unsigned int id) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class State {
|
||||||
|
initial,
|
||||||
|
requesting,
|
||||||
|
syncronized
|
||||||
|
};
|
||||||
|
|
||||||
|
State state;
|
||||||
|
std::deque<Asset> records;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user