draft: updated version for debug

This commit is contained in:
Mitternacht822
2025-11-28 12:45:50 +04:00
parent f821bcb01f
commit 71705f435b
6 changed files with 238 additions and 44 deletions

View File

@@ -576,6 +576,18 @@ static QMutex qrDecodeMutex;
// static
bool ImportController::decodeQrCode(const QString &code)
{
//old version
/*QMutexLocker lock(&qrDecodeMutex);
if (!mInstance->m_isQrCodeProcessed) {
mInstance->m_qrCodeChunks.clear();
mInstance->m_isQrCodeProcessed = true;
mInstance->m_totalQrCodeChunksCount = 0;
mInstance->m_receivedQrCodeChunksCount = 0;
}
return mInstance->parseQrCodeChunk(code);*/
//new version for transferController Qr
QMutexLocker lock(&qrDecodeMutex);
if (!mInstance->m_isQrCodeProcessed) {
@@ -584,7 +596,26 @@ bool ImportController::decodeQrCode(const QString &code)
mInstance->m_totalQrCodeChunksCount = 0;
mInstance->m_receivedQrCodeChunksCount = 0;
}
return mInstance->parseQrCodeChunk(code);
// trying as old QR with config chunks
if (mInstance->parseQrCodeChunk(code)) {
return true;
}
//if not chinked-QR, trying transferController QR
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
if (err.error == QJsonParseError::NoError && doc.isObject()) {
const QJsonObject obj = doc.object();
if (obj.contains(QStringLiteral("gw")) && obj.contains(QStringLiteral("u"))) {
// это наш QR для передачи устройства
mInstance->m_isQrCodeProcessed = false; // reset state
emit mInstance->transferQrDecoded(code); // send string to QML
return true;
}
}
return false;
}
#endif

View File

@@ -57,6 +57,7 @@ signals:
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
void qrDecodingFinished();
void transferQrDecoded(const QString &code);
void restoreAppConfig(const QByteArray &data);

View File

@@ -5,6 +5,7 @@
#include <QUrlQuery>
#include <QtConcurrent/QtConcurrentRun>
#include <QThread>
#include <QDebug>
#include "settings.h"
#include "ui/models/servers_model.h"
@@ -19,6 +20,21 @@ TransferController::TransferController(const std::shared_ptr<Settings> &settings
QObject *parent)
: QObject(parent), m_settings(settings), m_serversModel(serversModel), m_exportController(exportController)
{
qDebug() << "TransferController created";
}
void TransferController::handleImportControllerDestroyed()
{
m_importController = nullptr;
stopWaitForConfig();
}
TransferController::~TransferController() {
m_stopWaiting.storeRelease(1);
if (m_waitFuture.isRunning())
m_waitFuture.waitForFinished();
if (m_postFuture.isRunning())
m_postFuture.waitForFinished();
}
QString TransferController::buildQrPayloadJson(const QString &gatewayUrl, const QString &uuid, int version) const
@@ -27,14 +43,23 @@ QString TransferController::buildQrPayloadJson(const QString &gatewayUrl, const
obj["gw"] = gatewayUrl;
obj["u"] = uuid;
obj["v"] = version;
qDebug() << "built QrPayload with GW = " << gatewayUrl
<< " uuid = " << uuid
<< " version = " << version;
return QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
}
void TransferController::generateNewQrCode()
{
qDebug() << "TransferController::generateNewQrCode: generating QR code";
QString gw = m_settings->getGatewayEndpoint();
if (!gw.endsWith('/')) gw.append('/');
if (!gw.endsWith('/')) {
gw.append('/');
}
qDebug() << "gateway: " << gw;
m_currentUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
qDebug() << "uuid: " <<m_currentUuid;
const QString payload = buildQrPayloadJson(gw, m_currentUuid, 1);
@@ -50,29 +75,70 @@ void TransferController::generateNewQrCode()
void TransferController::stopScanner()
{
qDebug() << "TransferController::stopScanner: emitting scannerShouldStop";
emit scannerShouldStop();
}
QString TransferController::getPremiumConfigToSend() const
{
Q_UNUSED(apiDefs::key::apiKey)
qDebug() << "TransferController:getPremiumConfigToSend() called with apiKey " << apiDefs::key::apiKey;
//Q_UNUSED(apiDefs::key::apiKey)
return m_exportController ? m_exportController->getConfig() : QString();
}
QString TransferController::getCurrentApiKey() const
{
/*qDebug() << "getting current ApiKey: ";
const int idx = m_serversModel ? m_serversModel->getProcessedServerIndex() : -1;
if (idx >= 0 && m_serversModel) {
const QJsonObject server = m_serversModel->getServerConfig(idx);
qDebug() << "server: " << server;
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
qDebug() << "apiConfig: " << apiConfig;
const QString key = apiConfig.value(apiDefs::key::apiKey).toString();
qDebug() << "key: " << key;
return key;
}
return QString();
qDebug() << "returned with zero value";
return QString();*/
const int idx = m_serversModel ? m_serversModel->getProcessedServerIndex() : -1;
if (idx < 0 || !m_serversModel) {
return QString();
}
const QJsonObject server = m_serversModel->getServerConfig(idx);
// если нужно — можешь оставить лог:
qDebug() << "server:" << server;
// 1. читаем api_config — он тебе нужен для vpn_key и прочего
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
// qDebug() << "apiConfig:" << apiConfig;
// 2. читаем auth_data с верхнего уровня
QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
// или если есть константа: apiDefs::key::authData
// QJsonObject authData = server.value(apiDefs::key::authData).toObject();
QString key = authData.value(apiDefs::key::apiKey).toString();
// на всякий случай можно добавить fallback, если где-то старый формат:
if (key.isEmpty()) {
// вдруг когда-то api_key лежал в api_config.auth_data
const QJsonObject nestedAuth =
apiConfig.value(QStringLiteral("auth_data")).toObject();
if (!nestedAuth.isEmpty()) {
key = nestedAuth.value(apiDefs::key::apiKey).toString();
}
}
return key;
}
void TransferController::onTransferQrScanned(const QString &code)
{
qDebug() << "TransferController has scanned the Qr";
if (m_postInFlight.loadAcquire()) {
// prevent duplicate POSTs
return;
@@ -81,29 +147,41 @@ void TransferController::onTransferQrScanned(const QString &code)
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
qWarning() << "TransferController::onTransferQrScanned: invalid QR JSON " << err.errorString();
emit postFailed(QStringLiteral("Invalid QR JSON"));
return;
}
const QJsonObject obj = doc.object();
QString gw = obj.value("gw").toString();
const QString uuid = obj.value("u").toString();
if (gw.isEmpty() || uuid.isEmpty()) {
qWarning() << "TransferController::onTransferQrScanned: QR missing gw or uuid";
emit postFailed(QStringLiteral("QR missing gw or uuid"));
return;
}
if (!gw.endsWith('/')) gw.append('/');
if (!gw.endsWith('/')) {
gw.append('/');
}
const QString apiKey = getCurrentApiKey();
qDebug() << "scanned apiKey: " << apiKey;
const QString config = getPremiumConfigToSend();
qDebug() << "config: " << config;
if (apiKey.isEmpty() || config.isEmpty()) {
qWarning() << "TransferController::onTransferQrScanned: no subscription key or config to send";
emit postFailed(QStringLiteral("No subscription key or config to send"));
return;
}
// Allow only premium (subscription) configs
bool isPremium = m_serversModel && m_serversModel->processedServerIsPremium();
qDebug() << "isPremium: " << isPremium;
bool isFromGatewayApi = m_serversModel && m_serversModel->getProcessedServerData("isServerFromGatewayApi").toBool();
qDebug() << "is from gatewayApi: " << isFromGatewayApi;
if (!isPremium && !isFromGatewayApi) {
qWarning() << "TransferController::onTransferQrScanned: premium subscription required";
emit postFailed(QStringLiteral("Premium subscription required"));
return;
}
@@ -113,6 +191,12 @@ void TransferController::onTransferQrScanned(const QString &code)
m_postFuture = QtConcurrent::run([this, gw, uuid, apiKey, config]() {
qDebug() << "entered QFuture section";
qDebug() << "gw: " << gw;
qDebug() << "uuid: " << uuid;
qDebug() << "apiKey: " << apiKey;
qDebug() << "config: " << config;
GatewayController gatewayController(m_settings->getGatewayEndpoint(),
m_settings->isDevGatewayEnv(),
apiDefs::requestTimeoutMsecs,
@@ -126,11 +210,14 @@ void TransferController::onTransferQrScanned(const QString &code)
q.addQueryItem(QStringLiteral("uuid"), uuid);
q.addQueryItem(QStringLiteral("api_key"), apiKey);
//const QString endpoint = QString("%1sendConfig?%2").arg(gw, q.query(QUrl::FullyEncoded));
const QString endpoint = QStringLiteral("%1waitConfig?%2")
const QString endpoint = QStringLiteral("%1sendConfig?%2")
.arg(gw)
.arg(q.query(QUrl::FullyEncoded));
qDebug() << "TransferController::onTransferQrScanned: sending POST to" << endpoint;
auto errorCode = gatewayController.post(endpoint, payload, responseBody);
qDebug() << "TransferController::onTransferQrScanned: POST finished with code"
<< static_cast<int>(errorCode);
QMetaObject::invokeMethod(this, [this, errorCode, responseBody]() {
m_postInFlight.storeRelease(0);
@@ -141,13 +228,16 @@ void TransferController::onTransferQrScanned(const QString &code)
const QJsonObject obj = doc.object();
if (obj.value("status").toString() == QStringLiteral("success")) {
qDebug() << "TransferController::onTransferQrScanned: gateway returned success";
emit postSucceeded();
stopScanner();
return;
}
}
qWarning() << "TransferController::onTransferQrScanned: gateway response error";
emit postFailed(QStringLiteral("Gateway response error"));
} else {
qWarning() << "TransferController::onTransferQrScanned: network error during POST";
emit postFailed(QStringLiteral("Network error"));
}
}, Qt::QueuedConnection);
@@ -161,27 +251,63 @@ QString TransferController::qrCodeUrl() const
void TransferController::startWaitForConfig(ImportController *importController)
{
m_importController = importController;
m_stopWaiting.storeRelease(0);
qDebug() << "GW endpoint" << m_settings->getGatewayEndpoint();
if (m_waitFuture.isRunning()) {
qWarning() << "startWaitForConfig called while previous wait is still running; ignoring";
return;
}
QString gw = m_settings->getGatewayEndpoint();
if (!gw.endsWith('/')) {
gw.append('/');
}
const QString uuid = m_currentUuid;
const QString apiKey = getCurrentApiKey();
qDebug() << "TransferController::startWaitForConfig: starting wait with uuid" << m_currentUuid;
const int generation = m_waitGeneration.loadAcquire();
qDebug() << "gw: " << gw;
qDebug() << "uuid: " << uuid;
qDebug() << "generation: " << generation;
if (uuid.isEmpty()) {
qDebug() << "error: no uuid";
emit waitError(QStringLiteral("No UUID"));
return;
}
m_waitFuture = QtConcurrent::run([this, gw, uuid, apiKey, generation]() {
int backoffMs = 500;
const int maxBackoffMs = 5000;
m_importController = importController;
if (m_importController) {
connect(m_importController, &QObject::destroyed,
this,
&TransferController::handleImportControllerDestroyed,
Qt::UniqueConnection);
}
m_stopWaiting.storeRelease(0);
m_waitFuture = QtConcurrent::run([this, gw, uuid, generation]() {
qDebug() << "started waiting for QFuture";
//int backoffMs = 500;
//const int maxBackoffMs = 5000;
const int pollIntervalMs = 6000;
qDebug() << "TransferController::startWaitForConfig: waiting for config with"
<< "gw =" << gw
<< "uuid =" << uuid
<< "generation =" << generation;
while (!m_stopWaiting.loadAcquire()) {
if (generation != m_waitGeneration.loadAcquire()) break;
if (generation != m_waitGeneration.loadAcquire()) {
break;
}
QUrlQuery q;
q.addQueryItem(QStringLiteral("uuid"), uuid);
const QString endpoint = QStringLiteral("%1waitConfig?%2")
.arg(gw)
.arg(q.query(QUrl::FullyEncoded));
qDebug() << "waitConfig endpoint:" << endpoint;
QByteArray responseBody;
GatewayController gatewayController(m_settings->getGatewayEndpoint(),
@@ -189,20 +315,7 @@ void TransferController::startWaitForConfig(ImportController *importController)
apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QUrlQuery q;
q.addQueryItem(QStringLiteral("uuid"), uuid);
q.addQueryItem(QStringLiteral("api_key"), apiKey);
//const QString endpoint = QString("%1waitConfig?%2").arg(gw, q.query(QUrl::FullyEncoded));
const QString endpoint = QStringLiteral("%1waitConfig?%2")
.arg(gw)
.arg(q.query(QUrl::FullyEncoded));
auto errorCode = gatewayController.get(endpoint, responseBody);
if (m_stopWaiting.loadAcquire()) break;
if (generation != m_waitGeneration.loadAcquire()) break;
if (errorCode == ErrorCode::NoError) {
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(responseBody, &err);
@@ -210,16 +323,22 @@ void TransferController::startWaitForConfig(ImportController *importController)
emit waitError(QStringLiteral("Gateway response error"));
} else {
const QJsonObject obj = doc.object();
const QString cfg = obj.value("config").toString();
const QString cfg = obj.value(QStringLiteral("config")).toString();
if (cfg == QStringLiteral("timeout")) {
backoffMs = 500;
// ничего не пришло → продолжаем ждать
QThread::msleep(pollIntervalMs);
continue;
}
if (!cfg.isEmpty()) {
QMetaObject::invokeMethod(this, [this, cfg]() {
if (!m_importController) return;
m_importController->extractConfigFromData(cfg);
if (!m_importController)
return;
if (!m_importController->extractConfigFromData(cfg)) {
emit waitError(QStringLiteral("Invalid config received from gateway"));
return;
}
m_importController->importConfig();
emit configApplied();
}, Qt::QueuedConnection);
@@ -230,14 +349,15 @@ void TransferController::startWaitForConfig(ImportController *importController)
emit waitError(QStringLiteral("Network error"));
}
QThread::msleep(static_cast<unsigned long>(backoffMs));
backoffMs = qMin(backoffMs * 2, maxBackoffMs);
QThread::msleep(pollIntervalMs);
//QThread::msleep(static_cast<unsigned long>(backoffMs));
//backoffMs = qMin(backoffMs * 2, maxBackoffMs);
}
});
}
void TransferController::stopWaitForConfig()
{
qDebug() << "TransferController::stopWaitForConfig: stop flag set";
m_stopWaiting.storeRelease(1);
}

View File

@@ -28,6 +28,7 @@ public:
const QSharedPointer<ServersModel> &serversModel,
ExportController *exportController,
QObject *parent = nullptr);
~TransferController() override;
Q_INVOKABLE void generateNewQrCode();
@@ -56,6 +57,7 @@ signals:
void postFailed(const QString &message);
private:
void handleImportControllerDestroyed();
QString buildQrPayloadJson(const QString &gatewayUrl, const QString &uuid, int version) const;
QString getPremiumConfigToSend() const;
QString m_pendingQrCode;

View File

@@ -50,6 +50,7 @@ PageType {
Layout.topMargin: 16
text: qsTr("Yes, share")
enabled: root.getAvailableCount() > 0 && TransferController.pendingQrCode !== ""
clickedFunc: function() {
if (TransferController.pendingQrCode !== "") {
@@ -80,6 +81,22 @@ PageType {
}
}
}
Connections {
target: TransferController
function onPostStarted() {
PageController.showInfoMessage(qsTr("Sending configuration..."))
}
function onPostSucceeded() {
PageController.showInfoMessage(qsTr("Configuration sent successfully"))
}
function onPostFailed(message) {
PageController.showErrorMessage(message)
}
}
}

View File

@@ -15,22 +15,17 @@ import "../Config"
PageType {
id: root
// Единая логика при входе на страницу
Component.onCompleted: {
// Страница имеет смысл только на мобилках
if (!GC.isMobile()) {
PageController.closePage()
return
}
// Проверяем наличие камеры
if (!SettingsController.isCameraPresent()) {
PageController.showErrorMessage(qsTr("Camera is not available on this device"))
PageController.closePage()
return
}
// Android: запускаем нативный сканер камеры
if (Qt.platform.os === "android"
&& typeof ImportController !== "undefined"
&& ImportController.startDecodingQr) {
@@ -73,13 +68,12 @@ PageType {
anchors.rightMargin: 16
anchors.bottomMargin: 16
anchors.topMargin: 16
visible: Qt.platform.os === "ios" // На Android используется нативная камера
visible: Qt.platform.os === "ios"
color: AmneziaStyle.color.transparent
border.color: AmneziaStyle.color.paleGray
border.width: 1
// ВАЖНО: ридер виден на всех мобильных платформах
QRCodeReader {
id: qrReader
@@ -93,6 +87,9 @@ PageType {
}
Component.onCompleted: {
if (Qt.platform.os !== "ios")
return
qrReader.setCameraSize(
Qt.rect(cameraArea.x,
cameraArea.y,
@@ -109,10 +106,36 @@ PageType {
Connections {
target: TransferController
function onScannerShouldStop() {
qrReader.stopReading()
if (Qt.platform.os === "ios") {
qrReader.stopReading()
} else if (Qt.platform.os === "android"
&& typeof ImportController !== "undefined"
&& ImportController.stopDecodingQr) {
ImportController.stopDecodingQr()
}
}
}
}
}
}
Connections {
target: ImportController
function onTransferQrDecoded(code) {
if (!code || code.length === 0)
return
TransferController.setPendingQrCode(code)
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
}
}
Component.onDestruction: {
if (Qt.platform.os === "android"
&& typeof ImportController !== "undefined"
&& ImportController.stopDecodingQr) {
ImportController.stopDecodingQr()
}
}
}