diff --git a/API/api.cpp b/API/api.cpp index 96ef283..9537778 100644 --- a/API/api.cpp +++ b/API/api.cpp @@ -40,7 +40,8 @@ API::API(const QUrl& address, QObject* parent): state(NoServer), accessToken(), renewToken(), - firstPoll() + firstPoll(), + pollReply() { firstPoll.setSingleShot(true); firstPoll.setInterval(2000); @@ -67,21 +68,8 @@ void API::startPolling() { if (state != Authenticating) throw std::runtime_error("Can not start polling in this state: " + std::to_string(state)); - 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(QByteArray("Authorization"), authorizationHeader); - request.setTransferTimeout(30000); - + sendPoll(); firstPoll.start(); - - QNetworkReply* reply = network.get(request); - connect(reply, &QNetworkReply::finished, - std::bind(&API::onPollFinished, this, reply) - ); } void API::setAddress(const QUrl& path) { @@ -146,6 +134,24 @@ QUrl API::createUrl(const QString &path) const { 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(network.get(request)); + connect( + pollReply.get(), &QNetworkReply::finished, + this, &API::onPollFinished, + Qt::QueuedConnection + ); +} + void API::test(const QString& path, const QJSValue& finished) { qDebug() << "Testing" << path; if (state == Offline) @@ -218,7 +224,7 @@ void API::sendLogin(const QString& login, const QString& password, const QJSValu setState(Authenticating); QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8()); connect(reply, &QNetworkReply::finished, - std::bind(&API::onLoginFinished, this, reply, finished) + std::bind(&API::onLoginFinished, this, reply, finished) ); } @@ -283,19 +289,32 @@ void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) { startPolling(); } -void API::onPollFinished(QNetworkReply *reply) { +void API::onPollFinished() { firstPoll.stop(); - std::unique_ptr rpl(reply); - QNetworkReply::NetworkError error = reply->error(); - std::optional data = readResult(reply); + QNetworkReply::NetworkError error = pollReply->error(); + std::optional data = readResult(pollReply.get()); + Codes::Poll code = Codes::Poll::unknownError; + if (validateResponse(data, resultStructure)) + code = Codes::convertPoll(data->value("result").toInt()); + + QString detail = Codes::description(code); if (error != QNetworkReply::NoError) - qDebug() << reply->errorString(); + qDebug() << pollReply->errorString() + ": " + detail; + else + qDebug() << "Poll finished: " + detail; - if (data) - qDebug() << data.value(); - - setState(NotAuthenticated); + switch (code) { + case Codes::Poll::success: + //todo handle the result + case Codes::Poll::timeout: + return sendPoll(); + 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 + pollReply.reset(); + return setState(NotAuthenticated); + } } void API::onFirstPollSuccess() { diff --git a/API/api.h b/API/api.h index 88f455e..d46c1a4 100644 --- a/API/api.h +++ b/API/api.h @@ -56,7 +56,7 @@ private slots: void onTestFinished(QNetworkReply* reply, const QUrl& addr, const QJSValue& finished); void onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const; void onLoginFinished(QNetworkReply* reply, const QJSValue& finished); - void onPollFinished(QNetworkReply* reply); + void onPollFinished(); void onFirstPollSuccess(); @@ -67,6 +67,7 @@ private: static bool validateResponse(const std::optional& data, const std::map& structure); void setState(State newState); QUrl createUrl(const QString& path) const; + void sendPoll(); private: QUrl address; @@ -75,4 +76,5 @@ private: QString accessToken; QString renewToken; QTimer firstPoll; + std::unique_ptr pollReply; }; diff --git a/API/codes.cpp b/API/codes.cpp index 296f22f..b402986 100644 --- a/API/codes.cpp +++ b/API/codes.cpp @@ -27,6 +27,14 @@ constexpr std::array login "Unknown login error" }); +constexpr std::array pollErrors({ + "New data from poll", + "Couldn't authorize polling: token error", + "Other device was authorized with the same token", + "Poll timeout, no new data", + "Unknown poll error" +}); + Codes::Register Codes::convertRegister (int source) { if (source < 0) { std::cerr << "Converting Register response code which is smaller than zero, falling back to unknownError" << std::endl; @@ -62,3 +70,21 @@ Codes::Login Codes::convertLogin(int source) { QString Codes::description(Login code) { return loginErrors[(uint8_t)code]; } + +Codes::Poll Codes::convertPoll(int source) { + if (source < 0) { + std::cerr << "Converting Poll response code which is smaller than zero, falling back to unknownError" << std::endl; + return Poll::unknownError; + } + + if (source > (int)Poll::unknownError) { + std::cerr << "Converting Poll response code which is bigger than biggest known, falling back to unknownError" << std::endl; + return Poll::unknownError; + } + + return static_cast(source); +} + +QString Codes::description(Poll code) { + return pollErrors[(uint8_t)code]; +} diff --git a/API/codes.h b/API/codes.h index a15f214..170e5ce 100644 --- a/API/codes.h +++ b/API/codes.h @@ -28,10 +28,20 @@ enum class Login { unknownError }; +enum class Poll { + success, + tokenProblem, + replace, + timeout, + unknownError +}; + Register convertRegister (int source); Login convertLogin (int source); +Poll convertPoll (int source); QString description (Register code); QString description (Login code); +QString description (Poll code); } diff --git a/qml/Application/Assets.qml b/qml/Application/Assets.qml new file mode 100644 index 0000000..6567c5a --- /dev/null +++ b/qml/Application/Assets.qml @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls + +Item { + Label { + anchors.centerIn: parent + text: "This is Assets screen" + font { + pixelSize: 24 + bold: true + } + } +} diff --git a/qml/Application/CMakeLists.txt b/qml/Application/CMakeLists.txt index ca58442..bc7389b 100644 --- a/qml/Application/CMakeLists.txt +++ b/qml/Application/CMakeLists.txt @@ -10,6 +10,10 @@ qt_add_qml_module(magpieApplication NO_PLUGIN QML_FILES Root.qml + Main.qml + Home.qml + Assets.qml + Records.qml ) target_link_libraries(magpie PRIVATE magpieApplication) diff --git a/qml/Application/Home.qml b/qml/Application/Home.qml new file mode 100644 index 0000000..7bc7711 --- /dev/null +++ b/qml/Application/Home.qml @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls + +Item { + Label { + anchors.centerIn: parent + text: "This is a Home screen" + font { + pixelSize: 24 + bold: true + } + } +} diff --git a/qml/Application/Main.qml b/qml/Application/Main.qml new file mode 100644 index 0000000..342d4c5 --- /dev/null +++ b/qml/Application/Main.qml @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + property string currentPage: "home" + RowLayout { + anchors.fill: parent + + Column { + id: menu + Layout.fillWidth: true + Layout.minimumWidth: 50 + Layout.preferredWidth: 100 + Layout.maximumWidth: 100 + Layout.alignment: Qt.AlignTop + + MenuItem { + width: parent.width + text: qsTr("Home") + icon.name: "home" + highlighted: currentPage === "home" + onTriggered: { + if (!this.highlighted) + content.replace(home) + } + } + MenuItem { + width: parent.width + text: qsTr("Assets") + icon.name: "document-properties" + highlighted: currentPage === "assets" + onTriggered: { + if (!this.highlighted) + content.replace(assets) + } + } + MenuItem { + width: parent.width + text: qsTr("Records") + icon.name: "system-search" + highlighted: currentPage === "records" + onTriggered: { + if (!this.highlighted) + content.replace(records) + } + } + } + + StackView { + id: content + initialItem: home + Layout.fillWidth: true + Layout.fillHeight: true + + Component { + id: home + Home { + StackView.onActivating: currentPage = "home" + } + } + + Component { + id: assets + Assets { + StackView.onActivating: currentPage = "assets" + } + } + + Component { + id: records + Records { + StackView.onActivating: currentPage = "records" + } + } + } + } +} diff --git a/qml/Application/Records.qml b/qml/Application/Records.qml new file mode 100644 index 0000000..16e6195 --- /dev/null +++ b/qml/Application/Records.qml @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls + +Item { + Label { + anchors.centerIn: parent + text: "This is Records screen" + font { + pixelSize: 24 + bold: true + } + } +} diff --git a/qml/Application/Root.qml b/qml/Application/Root.qml index daba079..ae16d82 100644 --- a/qml/Application/Root.qml +++ b/qml/Application/Root.qml @@ -4,13 +4,17 @@ import QtQuick import QtQuick.Controls -Page { - Label { - anchors.centerIn: parent - text: "Here we go!" - font { - pixelSize: 24 - bold: true +Item { + StackView { + id: stack + initialItem: main + anchors.fill: parent + } + + Component { + id: main + Main { + } } } diff --git a/qml/main.qml b/qml/main.qml index a0f137e..d45c550 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -13,17 +13,13 @@ import magpie.Application as Application ApplicationWindow { property int counter: 0 property bool pickingServer: false + property bool runningApp: false width: 640 height: 480 visible: true title: "Magpie" - header: Label { - text: stack.currentItem.title - horizontalAlignment: Text.AlignHCenter - } - StackView { id: stack initialItem: welcome @@ -49,7 +45,8 @@ ApplicationWindow { Component { id: app Application.Root { - + StackView.onActivating: runningApp = true; + StackView.onDeactivating: runningApp = false; } } } @@ -62,7 +59,9 @@ ApplicationWindow { } function onStateChanged (state) { if (state === API.Authenticated) - stack.push(app) + stack.push(app); + else if (runningApp && state === API.NotAuthenticated) + stack.pop(); } } }