Add SettingsDAO implementation, schema migration, and related updates
Implemented `SettingsDAO` with `get`, `set`, `remove`, and related methods for key-value storage. Added database schema migration for a new `settings` table. Updated `EventDAO` with error handling improvements and `AbstractDAO` with helper utilities. Enhanced Ozon scripts with ad banner detection and handling.
This commit is contained in:
parent
8eedd07ffd
commit
3d3ed9cbf6
@ -15,14 +15,10 @@ QString AdbUtils::adbPath() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AdbUtils::startProcess(const QString &cmd, QString *stdOut = nullptr, QString *stdErr = nullptr) {
|
||||
bool AdbUtils::startProcess(const QString &program, const QStringList &args,
|
||||
QString *stdOut, QString *stdErr) {
|
||||
QProcess process;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
process.start("cmd.exe", {"/C", cmd});
|
||||
#else
|
||||
process.start("sh", {"-c", cmd});
|
||||
#endif
|
||||
process.start(program, args);
|
||||
|
||||
if (!process.waitForFinished() ||
|
||||
process.exitStatus() != QProcess::NormalExit ||
|
||||
@ -130,41 +126,34 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, int idx, bool scre
|
||||
QString output;
|
||||
|
||||
// 1. Dump UI hierarchy
|
||||
QString dumpCmd = QString("%1 -s %2 shell uiautomator dump /sdcard/uidump.xml").arg(adb, deviceId);
|
||||
if (!startProcess(dumpCmd)) {
|
||||
if (!startProcess(adb, {"-s", deviceId, "shell", "uiautomator", "dump", "/sdcard/uidump.xml"})) {
|
||||
qWarning() << "Failed to dump UI hierarchy.";
|
||||
return {};
|
||||
}
|
||||
// 2. Optionally pull and save the XML file
|
||||
if (screenshot) {
|
||||
QString pullCmd = QString("%1 -s %2 pull /sdcard/uidump.xml step_%3.xml").arg(adb, deviceId).arg(idx);
|
||||
if (!startProcess(pullCmd)) {
|
||||
if (!startProcess(adb, {"-s", deviceId, "pull", "/sdcard/uidump.xml",
|
||||
QString("step_%1.xml").arg(idx)})) {
|
||||
qWarning() << "Failed to pull UIDump XML.";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// 3. Read the XML content
|
||||
QString catCmd = QString("%1 -s %2 shell cat /sdcard/uidump.xml").arg(adb, deviceId);
|
||||
QString xmlOut;
|
||||
if (!startProcess(catCmd, &xmlOut)) {
|
||||
if (!startProcess(adb, {"-s", deviceId, "shell", "cat", "/sdcard/uidump.xml"}, &xmlOut)) {
|
||||
qWarning() << "Failed to read UIDump XML.";
|
||||
return {};
|
||||
}
|
||||
|
||||
// 4. Remove temp XML file
|
||||
QString rmCmd = QString("%1 -s %2 shell rm /sdcard/uidump.xml").arg(adb, deviceId);
|
||||
startProcess(rmCmd); // неважно, успешна ли команда
|
||||
startProcess(adb, {"-s", deviceId, "shell", "rm", "/sdcard/uidump.xml"}); // неважно, успешна ли команда
|
||||
|
||||
// 5. Screenshot (if needed)
|
||||
if (screenshot) {
|
||||
QString shotCmds[] = {
|
||||
QString("%1 -s %2 shell screencap -p /sdcard/screen.png").arg(adb, deviceId),
|
||||
QString("%1 -s %2 pull /sdcard/screen.png screen_step_%3.png").arg(adb, deviceId).arg(idx),
|
||||
QString("%1 -s %2 shell rm /sdcard/screen.png").arg(adb, deviceId)
|
||||
};
|
||||
for (const QString &cmd: shotCmds) {
|
||||
startProcess(cmd); // ошибки игнорируем для скриншота
|
||||
}
|
||||
startProcess(adb, {"-s", deviceId, "shell", "screencap", "-p", "/sdcard/screen.png"});
|
||||
startProcess(adb, {"-s", deviceId, "pull", "/sdcard/screen.png",
|
||||
QString("screen_step_%1.png").arg(idx)});
|
||||
startProcess(adb, {"-s", deviceId, "shell", "rm", "/sdcard/screen.png"});
|
||||
}
|
||||
|
||||
// 6. Обрезка к началу XML
|
||||
@ -177,16 +166,17 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, int idx, bool scre
|
||||
|
||||
bool AdbUtils::takeScreenshot(const QString &deviceId, const QString &name) {
|
||||
const QString adb = adbPath();
|
||||
QStringList commands = {
|
||||
QString("%1 -s %2 shell screencap -p /sdcard/screen.png").arg(adb, deviceId),
|
||||
QString("%1 -s %2 pull /sdcard/screen.png %3.png").arg(adb, deviceId, name),
|
||||
QString("%1 -s %2 shell rm /sdcard/screen.png").arg(adb, deviceId)
|
||||
struct Step { QStringList args; };
|
||||
Step steps[] = {
|
||||
{{"-s", deviceId, "shell", "screencap", "-p", "/sdcard/screen.png"}},
|
||||
{{"-s", deviceId, "pull", "/sdcard/screen.png", name + ".png"}},
|
||||
{{"-s", deviceId, "shell", "rm", "/sdcard/screen.png"}}
|
||||
};
|
||||
|
||||
for (const QString &cmd: commands) {
|
||||
for (const auto &step : steps) {
|
||||
QString stdErr;
|
||||
if (!startProcess(cmd, nullptr, &stdErr)) {
|
||||
qWarning() << "ADB command failed:" << cmd;
|
||||
if (!startProcess(adb, step.args, nullptr, &stdErr)) {
|
||||
qWarning() << "ADB command failed:" << step.args.join(' ');
|
||||
qWarning() << "Error:" << stdErr;
|
||||
return false;
|
||||
}
|
||||
@ -195,13 +185,8 @@ bool AdbUtils::takeScreenshot(const QString &deviceId, const QString &name) {
|
||||
}
|
||||
|
||||
QByteArray AdbUtils::captureScreenshotBytes(const QString &deviceId) {
|
||||
const QString cmd = QString("%1 -s %2 exec-out screencap -p").arg(adbPath(), deviceId);
|
||||
QProcess process;
|
||||
#ifdef Q_OS_WIN
|
||||
process.start("cmd.exe", {"/C", cmd});
|
||||
#else
|
||||
process.start("sh", {"-c", cmd});
|
||||
#endif
|
||||
process.start(adbPath(), {"-s", deviceId, "exec-out", "screencap", "-p"});
|
||||
if (!process.waitForFinished(10000) || process.exitCode() != 0) {
|
||||
qWarning() << "[AdbUtils] captureScreenshotBytes failed for" << deviceId;
|
||||
return {};
|
||||
@ -211,17 +196,18 @@ QByteArray AdbUtils::captureScreenshotBytes(const QString &deviceId) {
|
||||
|
||||
bool AdbUtils::takeXmlDump(const QString &deviceId, const QString &name) {
|
||||
const QString adb = adbPath();
|
||||
QStringList commands = {
|
||||
QString("%1 -s %2 shell uiautomator dump /sdcard/uidump.xml").arg(adb, deviceId),
|
||||
QString("%1 -s %2 pull /sdcard/uidump.xml %3.xml").arg(adb, deviceId, name),
|
||||
QString("%1 -s %2 shell cat /sdcard/uidump.xml").arg(adb, deviceId),
|
||||
QString("%1 -s %2 shell rm /sdcard/uidump.xml").arg(adb, deviceId)
|
||||
struct Step { QStringList args; };
|
||||
Step steps[] = {
|
||||
{{"-s", deviceId, "shell", "uiautomator", "dump", "/sdcard/uidump.xml"}},
|
||||
{{"-s", deviceId, "pull", "/sdcard/uidump.xml", name + ".xml"}},
|
||||
{{"-s", deviceId, "shell", "cat", "/sdcard/uidump.xml"}},
|
||||
{{"-s", deviceId, "shell", "rm", "/sdcard/uidump.xml"}}
|
||||
};
|
||||
|
||||
for (const QString &cmd: commands) {
|
||||
for (const auto &step : steps) {
|
||||
QString stdErr;
|
||||
if (!startProcess(cmd, nullptr, &stdErr)) {
|
||||
qWarning() << "ADB command failed:" << cmd;
|
||||
if (!startProcess(adb, step.args, nullptr, &stdErr)) {
|
||||
qWarning() << "ADB command failed:" << step.args.join(' ');
|
||||
qWarning() << "Error:" << stdErr;
|
||||
return false;
|
||||
}
|
||||
@ -242,15 +228,16 @@ bool AdbUtils::makeTap(const QString &deviceId, const int x, const int y) {
|
||||
|
||||
bool AdbUtils::enableAdbIMEKeyboard(const QString &deviceId) {
|
||||
const QString adb = adbPath();
|
||||
QStringList commands = {
|
||||
QString("%1 -s %2 shell ime enable com.android.adbkeyboard/.AdbIME").arg(adb, deviceId),
|
||||
QString("%1 -s %2 shell ime set com.android.adbkeyboard/.AdbIME").arg(adb, deviceId)
|
||||
struct Step { QStringList args; };
|
||||
Step steps[] = {
|
||||
{{"-s", deviceId, "shell", "ime", "enable", "com.android.adbkeyboard/.AdbIME"}},
|
||||
{{"-s", deviceId, "shell", "ime", "set", "com.android.adbkeyboard/.AdbIME"}}
|
||||
};
|
||||
|
||||
for (const QString &cmd: commands) {
|
||||
for (const auto &step : steps) {
|
||||
QString stdErr;
|
||||
if (!startProcess(cmd, nullptr, &stdErr)) {
|
||||
qWarning() << "ADB command failed:" << cmd;
|
||||
if (!startProcess(adb, step.args, nullptr, &stdErr)) {
|
||||
qWarning() << "ADB command failed:" << step.args.join(' ');
|
||||
qWarning() << "Error:" << stdErr;
|
||||
return false;
|
||||
}
|
||||
@ -275,10 +262,8 @@ bool AdbUtils::inputAdbIMEText(const QString &deviceId, const QString &text) {
|
||||
|
||||
bool AdbUtils::disableAdbIMEKeyboard(const QString &deviceId) {
|
||||
QProcess process;
|
||||
QString command = QString(
|
||||
"%1 -s %2 shell ime disable com.android.adbkeyboard/.AdbIME"
|
||||
).arg(adbPath(), deviceId);
|
||||
process.start(command);
|
||||
process.start(adbPath(), {"-s", deviceId, "shell", "ime", "disable",
|
||||
"com.android.adbkeyboard/.AdbIME"});
|
||||
process.waitForFinished();
|
||||
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||
}
|
||||
@ -290,7 +275,9 @@ bool AdbUtils::makeSwipe(const QString &deviceId, const int x1, const int y1, co
|
||||
{
|
||||
"-s", deviceId,
|
||||
"shell", "input",
|
||||
"swipe", QString("%1 %2 %3 %4").arg(x1).arg(y1).arg(x2).arg(y2)
|
||||
"swipe",
|
||||
QString::number(x1), QString::number(y1),
|
||||
QString::number(x2), QString::number(y2)
|
||||
}
|
||||
);
|
||||
process.waitForFinished();
|
||||
|
||||
@ -47,5 +47,6 @@ public:
|
||||
static QSet<QString> getListApps(const QString &deviceId);
|
||||
|
||||
private:
|
||||
static bool startProcess(const QString &cmd, QString *stdOut, QString *stdErr);
|
||||
static bool startProcess(const QString &program, const QStringList &args,
|
||||
QString *stdOut = nullptr, QString *stdErr = nullptr);
|
||||
};
|
||||
|
||||
@ -110,6 +110,14 @@ bool CommonScript::runAppAndGoToHomeScreen(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверяем на рекламный bottom sheet (ипотека и т.п.)
|
||||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||||
qDebug() << "[runAppAndGoToHomeScreen] Ad bottom sheet detected, closing...";
|
||||
AdbUtils::makeTap(deviceId, adNode.x(), adNode.y());
|
||||
QThread::msleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверяем, что это экран ввода пин-кода
|
||||
if (!findAndInputPincode) {
|
||||
if (const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
#include "net/NetworkService.h"
|
||||
#include "AppLogger.h"
|
||||
|
||||
@ -155,7 +156,7 @@ void GetCardInfoScript::doStart() {
|
||||
// 1. Создаём девайс если нет api_id
|
||||
if (device.apiId.isEmpty()) {
|
||||
QJsonObject deviceBody;
|
||||
deviceBody["desktop_id"] = QSettings("config.ini", QSettings::IniFormat).value("common/desktop_id").toString();
|
||||
deviceBody["desktop_id"] = SettingsDAO::get("desktop_id");
|
||||
deviceBody["status"] = "ONLINE";
|
||||
deviceBody["name"] = device.name;
|
||||
deviceBody["screen_width"] = device.screenWidth;
|
||||
@ -261,12 +262,18 @@ void GetCardInfoScript::doStart() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Закрываем баннер "Понятно" если он перекрывает экран
|
||||
// Закрываем баннер "Понятно" и повторно нажимаем "Показать"
|
||||
Node bannerBtn = xmlScreenParser.findTextNode("Понятно");
|
||||
if (!bannerBtn.isEmpty()) {
|
||||
qDebug() << "[GetCardInfo] Banner 'Понятно' detected, closing...";
|
||||
AdbUtils::makeTap(m_deviceId, bannerBtn.x(), bannerBtn.y());
|
||||
QThread::msleep(1000);
|
||||
QThread::msleep(1500);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
Node retryShowBtn = xmlScreenParser.findButtonNode("Показать", false);
|
||||
if (!retryShowBtn.isEmpty()) {
|
||||
qDebug() << "[GetCardInfo] Re-tapping 'Показать' after banner close";
|
||||
AdbUtils::makeTap(m_deviceId, retryShowBtn.x(), retryShowBtn.y());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@ -412,22 +412,46 @@ QList<Node> ScreenXmlParser::findPinCodeNodes(const QString &pinCode) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Способ 2 (fallback): анонимные FrameLayout — ищем сетку 4x3
|
||||
QList<Node> clickable;
|
||||
// Способ 2 (fallback): группируем clickable-ноды одинакового размера,
|
||||
// ищем сетку 4x3 по паттерну (не привязываясь к координатам экрана)
|
||||
QMap<QPair<int,int>, QList<Node>> sizeGroups;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.clickable && node.width() > 0 && node.height() > 0)
|
||||
clickable.append(node);
|
||||
if (!node.clickable || node.width() <= 0 || node.height() <= 0) continue;
|
||||
if (node.width() == node.height()) { // кнопки пин-пада квадратные
|
||||
sizeGroups[{node.width(), node.height()}].append(node);
|
||||
}
|
||||
}
|
||||
if (clickable.isEmpty()) return {};
|
||||
|
||||
int minH = INT_MAX;
|
||||
for (const Node &n : clickable) minH = std::min(minH, n.height());
|
||||
|
||||
// Ищем группу из >= 12 одинаковых кнопок (пин-пад: 3x4 = 12)
|
||||
QList<Node> candidates;
|
||||
for (const Node &n : clickable) {
|
||||
if (n.height() <= minH * 3) candidates.append(n);
|
||||
for (auto it = sizeGroups.begin(); it != sizeGroups.end(); ++it) {
|
||||
if (it.value().size() >= 12 &&
|
||||
(candidates.isEmpty() || it.value().size() < candidates.size())) {
|
||||
candidates = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.size() < 12) {
|
||||
// Попробуем без ограничения на квадрат, но с >= 12
|
||||
sizeGroups.clear();
|
||||
for (const Node &node : m_nodes) {
|
||||
if (!node.clickable || node.width() <= 0 || node.height() <= 0) continue;
|
||||
sizeGroups[{node.width(), node.height()}].append(node);
|
||||
}
|
||||
for (auto it = sizeGroups.begin(); it != sizeGroups.end(); ++it) {
|
||||
if (it.value().size() >= 12 &&
|
||||
(candidates.size() < 12 || it.value().size() < candidates.size())) {
|
||||
candidates = it.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.size() < 12) {
|
||||
qWarning() << "[findPinCodeNodes] grid not found: no group of 12+ same-sized clickable nodes";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Сортируем по Y, затем группируем в строки
|
||||
std::sort(candidates.begin(), candidates.end(), [](const Node &a, const Node &b) {
|
||||
return a.y() < b.y();
|
||||
});
|
||||
@ -457,7 +481,8 @@ QList<Node> ScreenXmlParser::findPinCodeNodes(const QString &pinCode) {
|
||||
}
|
||||
|
||||
if (gridRows.size() < 4) {
|
||||
qWarning() << "[findPinCodeNodes] grid not found: gridRows=" << gridRows.size();
|
||||
qWarning() << "[findPinCodeNodes] grid not found: gridRows=" << gridRows.size()
|
||||
<< "candidates=" << candidates.size();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
BIN
assets/ozon/banner_ipoteka.png
Normal file
BIN
assets/ozon/banner_ipoteka.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
1
assets/ozon/banner_ipoteka.xml
Normal file
1
assets/ozon/banner_ipoteka.xml
Normal file
File diff suppressed because one or more lines are too long
@ -66,14 +66,189 @@
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="1" hint="">
|
||||
<node NAF="true" index="0" text="" resource-id=""
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.webkit.WebView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="true"
|
||||
enabled="true" focusable="true" focused="false"
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]" drawing-order="1"
|
||||
hint=""/>
|
||||
selected="false" bounds="[0,0][1080,2337]" drawing-order="0"
|
||||
hint="">
|
||||
<node index="0" text="⠀" resource-id=""
|
||||
class="android.webkit.WebView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="true" focused="true"
|
||||
scrollable="true" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="svelte"
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,241]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="base-go-back-button"
|
||||
checkable="false" checked="false"
|
||||
clickable="true" enabled="true"
|
||||
focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[28,144][118,231]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="native-toolbar-close-button"
|
||||
checkable="false" checked="false"
|
||||
clickable="true" enabled="true"
|
||||
focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[1036,186][1080,241]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[480,1349][598,1470]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.Image"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[485,1354][593,1462]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="2" text=""
|
||||
resource-id="modal-container"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,2337][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="3" text=""
|
||||
resource-id="svelte-announcer"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][5,5]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="3" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
|
||||
@ -2,13 +2,7 @@
|
||||
list=ozon
|
||||
|
||||
[common]
|
||||
access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNDU1MjE1Ni1kMTc5LTRhNzktOWU5MS01ODcwMzhjOTVjZjAiLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcyODg4MjU2fQ.5WF3EQobCOQpuQr5wcCeYOJW1ER0RJG506WRhEB_Y5E
|
||||
api_key=0eee77b62013253e02e1a69bc6a505bb37757fb7
|
||||
currencies=USD, KRW, AZN, RUB
|
||||
desktop_id=b86eac8e-dc89-4a9e-a5e8-7f85db011391
|
||||
profile_id=c4552156-d179-4a79-9e91-587038c95cf0
|
||||
refresh_token=4c78f097-d70b-4bdb-b351-c839fa9eb442
|
||||
token_expires_at=1772888256
|
||||
version=0.0.1
|
||||
|
||||
[db]
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
#include <QRegularExpression>
|
||||
|
||||
DatabaseManager::DatabaseManager() = default;
|
||||
|
||||
@ -75,6 +76,13 @@ QString DatabaseManager::connectionName() {
|
||||
|
||||
|
||||
static bool columnExists(QSqlQuery &q, const QString &table, const QString &column) {
|
||||
// Валидация: допускаем только [a-zA-Z0-9_] чтобы исключить SQL injection
|
||||
static const QRegularExpression valid("^[a-zA-Z_][a-zA-Z0-9_]*$");
|
||||
if (!valid.match(table).hasMatch() || !valid.match(column).hasMatch()) {
|
||||
qCritical() << "[DB] columnExists: invalid identifier" << table << column;
|
||||
return false;
|
||||
}
|
||||
|
||||
q.exec(QString("SELECT COUNT(*) FROM pragma_table_info('%1') WHERE name='%2'")
|
||||
.arg(table, column));
|
||||
return q.next() && q.value(0).toInt() > 0;
|
||||
@ -88,7 +96,7 @@ void DatabaseManager::initSchema() {
|
||||
q.exec("PRAGMA user_version");
|
||||
const int version = q.next() ? q.value(0).toInt() : 0;
|
||||
|
||||
// ── Версия 1: начальная схема + api_id для devices ────────────────────────
|
||||
// ── Версия 1: полная схема ─────────────────────────────────────────────────
|
||||
if (version < 1) {
|
||||
db.transaction();
|
||||
|
||||
@ -117,6 +125,11 @@ void DatabaseManager::initSchema() {
|
||||
status TEXT DEFAULT 'off',
|
||||
install TEXT DEFAULT 'no',
|
||||
comment TEXT,
|
||||
phone TEXT DEFAULT '',
|
||||
full_name TEXT DEFAULT '',
|
||||
bank_profile_id TEXT DEFAULT '',
|
||||
currency TEXT DEFAULT '',
|
||||
amount REAL DEFAULT 0,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, code)
|
||||
))");
|
||||
@ -131,6 +144,8 @@ void DatabaseManager::initSchema() {
|
||||
currency TEXT DEFAULT '',
|
||||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||
status TEXT DEFAULT 'active',
|
||||
card_number TEXT DEFAULT '',
|
||||
material_id TEXT DEFAULT '',
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, app_code, last_numbers)
|
||||
))");
|
||||
@ -155,185 +170,11 @@ void DatabaseManager::initSchema() {
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
receipt_image BLOB,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER,
|
||||
external_id INTEGER,
|
||||
device_id TEXT,
|
||||
amount REAL,
|
||||
phone TEXT,
|
||||
bank_name TEXT,
|
||||
comment TEXT,
|
||||
status TEXT,
|
||||
type TEXT,
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (external_id)
|
||||
))");
|
||||
|
||||
// Добавляем api_id в devices если ещё нет (для существующих БД)
|
||||
if (!columnExists(q, "devices", "api_id")) {
|
||||
q.exec("ALTER TABLE devices ADD COLUMN api_id TEXT");
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 1");
|
||||
qDebug() << "[DB] Migration 1 applied";
|
||||
}
|
||||
|
||||
// ── Версия 2: currency в accounts + переименование app_name → app_code ───
|
||||
if (version < 2) {
|
||||
db.transaction();
|
||||
|
||||
// Добавляем currency если ещё нет (существующие БД без этого поля)
|
||||
if (!columnExists(q, "accounts", "currency")) {
|
||||
q.exec("ALTER TABLE accounts ADD COLUMN currency TEXT DEFAULT ''");
|
||||
}
|
||||
|
||||
// Переименовываем app_name → app_code (SQLite 3.25+)
|
||||
if (columnExists(q, "accounts", "app_name")) {
|
||||
q.exec("ALTER TABLE accounts RENAME COLUMN app_name TO app_code");
|
||||
if (q.lastError().isValid()) {
|
||||
qWarning() << "[DB] Migration 2: rename app_name failed:" << q.lastError().text();
|
||||
}
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 2");
|
||||
qDebug() << "[DB] Migration 2 applied";
|
||||
}
|
||||
|
||||
// ── Версия 3: phone и full_name в applications ────────────────────────────
|
||||
if (version < 3) {
|
||||
db.transaction();
|
||||
|
||||
if (!columnExists(q, "applications", "phone")) {
|
||||
q.exec("ALTER TABLE applications ADD COLUMN phone TEXT DEFAULT ''");
|
||||
}
|
||||
if (!columnExists(q, "applications", "full_name")) {
|
||||
q.exec("ALTER TABLE applications ADD COLUMN full_name TEXT DEFAULT ''");
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 3");
|
||||
qDebug() << "[DB] Migration 3 applied";
|
||||
}
|
||||
|
||||
// ── Версия 4: card_number в accounts ──────────────────────────────────────
|
||||
if (version < 4) {
|
||||
db.transaction();
|
||||
|
||||
if (!columnExists(q, "accounts", "card_number")) {
|
||||
q.exec("ALTER TABLE accounts ADD COLUMN card_number TEXT DEFAULT ''");
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 4");
|
||||
qDebug() << "[DB] Migration 4 applied";
|
||||
}
|
||||
|
||||
// ── Версия 5: bank_profile_id в applications ──────────────────────────────
|
||||
if (version < 5) {
|
||||
db.transaction();
|
||||
|
||||
if (!columnExists(q, "applications", "bank_profile_id")) {
|
||||
q.exec("ALTER TABLE applications ADD COLUMN bank_profile_id TEXT DEFAULT ''");
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 5");
|
||||
qDebug() << "[DB] Migration 5 applied";
|
||||
}
|
||||
|
||||
// ── Версия 6: currency и amount в applications ─────────────────────────────
|
||||
if (version < 6) {
|
||||
db.transaction();
|
||||
|
||||
if (!columnExists(q, "applications", "currency")) {
|
||||
q.exec("ALTER TABLE applications ADD COLUMN currency TEXT DEFAULT ''");
|
||||
}
|
||||
if (!columnExists(q, "applications", "amount")) {
|
||||
q.exec("ALTER TABLE applications ADD COLUMN amount REAL DEFAULT 0");
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 6");
|
||||
qDebug() << "[DB] Migration 6 applied";
|
||||
}
|
||||
|
||||
// ── Версия 7: material_id в accounts ──────────────────────────────────────
|
||||
if (version < 7) {
|
||||
db.transaction();
|
||||
|
||||
if (!columnExists(q, "accounts", "material_id")) {
|
||||
q.exec("ALTER TABLE accounts ADD COLUMN material_id TEXT DEFAULT ''");
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 7");
|
||||
qDebug() << "[DB] Migration 7 applied";
|
||||
}
|
||||
|
||||
// ── Версия 8: receipt_image в transactions ──────────────────────────────────
|
||||
if (version < 8) {
|
||||
db.transaction();
|
||||
|
||||
if (!columnExists(q, "transactions", "receipt_image")) {
|
||||
q.exec("ALTER TABLE transactions ADD COLUMN receipt_image BLOB");
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 8");
|
||||
qDebug() << "[DB] Migration 8 applied";
|
||||
}
|
||||
|
||||
// ── Версия 9: таблица banks ───────────────────────────────────────────────
|
||||
if (version < 9) {
|
||||
db.transaction();
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS banks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
alias TEXT UNIQUE,
|
||||
name TEXT,
|
||||
nspk_name TEXT
|
||||
))");
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 9");
|
||||
qDebug() << "[DB] Migration 9 applied";
|
||||
}
|
||||
|
||||
// ── Версия 10: таблица app_logs ───────────────────────────────────────────
|
||||
if (version < 10) {
|
||||
db.transaction();
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS app_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
device_id TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
screenshot BLOB,
|
||||
timestamp TEXT NOT NULL
|
||||
))");
|
||||
|
||||
q.exec("CREATE INDEX IF NOT EXISTS idx_app_logs_ts ON app_logs (id DESC)");
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 10");
|
||||
qDebug() << "[DB] Migration 10 applied";
|
||||
}
|
||||
|
||||
// ── Версия 11: убираем UNIQUE(external_id) из events ─────────────────────
|
||||
if (version < 11) {
|
||||
db.transaction();
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS events_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER,
|
||||
external_id TEXT,
|
||||
@ -347,29 +188,38 @@ void DatabaseManager::initSchema() {
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
json TEXT DEFAULT '',
|
||||
sent_to_server INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||
))");
|
||||
|
||||
q.exec(R"(INSERT INTO events_new
|
||||
SELECT * FROM events)");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS banks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
alias TEXT UNIQUE,
|
||||
name TEXT,
|
||||
nspk_name TEXT
|
||||
))");
|
||||
|
||||
q.exec("DROP TABLE events");
|
||||
q.exec("ALTER TABLE events_new RENAME TO events");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS app_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
device_id TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
screenshot BLOB,
|
||||
timestamp TEXT NOT NULL
|
||||
))");
|
||||
|
||||
q.exec("CREATE INDEX IF NOT EXISTS idx_app_logs_ts ON app_logs (id DESC)");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
))");
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 11");
|
||||
qDebug() << "[DB] Migration 11 applied";
|
||||
}
|
||||
|
||||
// ── Версия 12: json и sent_to_server в events ────────────────────────────
|
||||
if (version < 12) {
|
||||
db.transaction();
|
||||
q.exec("ALTER TABLE events ADD COLUMN json TEXT DEFAULT ''");
|
||||
q.exec("ALTER TABLE events ADD COLUMN sent_to_server INTEGER DEFAULT 0");
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 12");
|
||||
qDebug() << "[DB] Migration 12 applied";
|
||||
q.exec("PRAGMA user_version = 1");
|
||||
qDebug() << "[DB] Migration 1 applied";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@ -58,7 +58,7 @@ bool AccountDAO::updateAccountById(
|
||||
|
||||
query.bindValue(":status", status);
|
||||
query.bindValue(":description", description);
|
||||
query.bindValue(":last_numbers", description.right(4));
|
||||
query.bindValue(":last_numbers", description.length() >= 4 ? description.right(4) : description);
|
||||
query.bindValue(":currency", currency);
|
||||
query.bindValue(":id", id);
|
||||
|
||||
|
||||
@ -203,7 +203,10 @@ QList<EventInfo> EventDAO::getEvents(int page, int pageSize) {
|
||||
|
||||
int EventDAO::getTotalCount() {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.exec("SELECT COUNT(*) FROM events");
|
||||
if (!query.exec("SELECT COUNT(*) FROM events")) {
|
||||
qWarning() << "Ошибка при получении getTotalCount:" << query.lastError().text();
|
||||
return 0;
|
||||
}
|
||||
if (query.next()) return query.value(0).toInt();
|
||||
return 0;
|
||||
}
|
||||
|
||||
55
database/dao/SettingsDAO.cpp
Normal file
55
database/dao/SettingsDAO.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "SettingsDAO.h"
|
||||
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QDebug>
|
||||
|
||||
#include "DatabaseManager.h"
|
||||
|
||||
QString SettingsDAO::get(const QString &key, const QString &defaultValue) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("SELECT value FROM settings WHERE key = :key LIMIT 1");
|
||||
query.bindValue(":key", key);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "SettingsDAO::get error:" << query.lastError().text();
|
||||
return defaultValue;
|
||||
}
|
||||
if (query.next()) {
|
||||
return query.value(0).toString();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void SettingsDAO::set(const QString &key, const QString &value) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
INSERT INTO settings (key, value) VALUES (:key, :value)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
||||
)");
|
||||
query.bindValue(":key", key);
|
||||
query.bindValue(":value", value);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "SettingsDAO::set error:" << query.lastError().text();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDAO::remove(const QString &key) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("DELETE FROM settings WHERE key = :key");
|
||||
query.bindValue(":key", key);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "SettingsDAO::remove error:" << query.lastError().text();
|
||||
}
|
||||
}
|
||||
|
||||
qint64 SettingsDAO::getLong(const QString &key, qint64 defaultValue) {
|
||||
const QString val = get(key);
|
||||
if (val.isEmpty()) return defaultValue;
|
||||
bool ok;
|
||||
const qint64 result = val.toLongLong(&ok);
|
||||
return ok ? result : defaultValue;
|
||||
}
|
||||
|
||||
void SettingsDAO::setLong(const QString &key, qint64 value) {
|
||||
set(key, QString::number(value));
|
||||
}
|
||||
12
database/dao/SettingsDAO.h
Normal file
12
database/dao/SettingsDAO.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
class SettingsDAO {
|
||||
public:
|
||||
static QString get(const QString &key, const QString &defaultValue = {});
|
||||
static void set(const QString &key, const QString &value);
|
||||
static void remove(const QString &key);
|
||||
static qint64 getLong(const QString &key, qint64 defaultValue = 0);
|
||||
static void setLong(const QString &key, qint64 value);
|
||||
};
|
||||
15
main.cpp
15
main.cpp
@ -17,6 +17,7 @@
|
||||
#include <QSettings>
|
||||
|
||||
#include "AppLogger.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
#include "EventHandler.h"
|
||||
#include "net/NetworkService.h"
|
||||
#include "ozon/PayByPhoneScript.h"
|
||||
@ -196,11 +197,11 @@ int main(int argc, char *argv[]) {
|
||||
// - неверный api_key [code 0] — показываем окно ввода
|
||||
// - всё ок [code 1] — запускаем приложение
|
||||
// 2. Если нет api_key — показываем окно для ввода
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const QString apiKey = settings.value("common/api_key").toString();
|
||||
const QString apiKey = SettingsDAO::get("api_key");
|
||||
|
||||
auto showRegistration = [&app]() {
|
||||
auto *registrationWindow = new RegistrationWindow();
|
||||
registrationWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
registrationWindow->show();
|
||||
QObject::connect(
|
||||
registrationWindow, &RegistrationWindow::onRegisteredSuccess, registrationWindow,
|
||||
@ -219,6 +220,7 @@ int main(int argc, char *argv[]) {
|
||||
showRegistration();
|
||||
} else {
|
||||
auto *loader = new LoaderWindow();
|
||||
loader->setAttribute(Qt::WA_DeleteOnClose);
|
||||
loader->show();
|
||||
|
||||
QObject::connect(loader, &LoaderWindow::loadingFinished, loader, [loader, &app, showRegistration](const int result) {
|
||||
@ -227,10 +229,12 @@ int main(int argc, char *argv[]) {
|
||||
"Сервер не отвечает.\n\nПроверьте подключение к интернету и попробуйте снова.",
|
||||
true
|
||||
);
|
||||
errorWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
errorWindow->show();
|
||||
QObject::connect(errorWindow, &ErrorWindow::retryRequested, errorWindow, [errorWindow, &app, showRegistration]() {
|
||||
errorWindow->close();
|
||||
auto *newLoader = new LoaderWindow();
|
||||
newLoader->setAttribute(Qt::WA_DeleteOnClose);
|
||||
newLoader->show();
|
||||
QObject::connect(newLoader, &LoaderWindow::loadingFinished, newLoader, [newLoader, &app, showRegistration](const int r) {
|
||||
if (r == 1) {
|
||||
@ -241,14 +245,14 @@ int main(int argc, char *argv[]) {
|
||||
startEventHandler(app);
|
||||
// startPayByPhoneTest();
|
||||
} else if (r == 0) {
|
||||
QSettings s("config.ini", QSettings::IniFormat);
|
||||
s.remove("common/api_key");
|
||||
SettingsDAO::remove("api_key");
|
||||
showRegistration();
|
||||
} else {
|
||||
auto *err2 = new ErrorWindow(
|
||||
"Сервер не отвечает.\n\nПроверьте подключение к интернету и попробуйте снова.",
|
||||
false
|
||||
);
|
||||
err2->setAttribute(Qt::WA_DeleteOnClose);
|
||||
err2->show();
|
||||
}
|
||||
newLoader->close();
|
||||
@ -262,8 +266,7 @@ int main(int argc, char *argv[]) {
|
||||
startEventHandler(app);
|
||||
// startPayByPhoneTest();
|
||||
} else if (result == 0) {
|
||||
QSettings s("config.ini", QSettings::IniFormat);
|
||||
s.remove("common/api_key");
|
||||
SettingsDAO::remove("api_key");
|
||||
showRegistration();
|
||||
}
|
||||
loader->close();
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <QBuffer>
|
||||
#include <qeventloop.h>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QImage>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QJsonObject>
|
||||
@ -18,6 +19,7 @@
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
|
||||
DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
@ -77,14 +79,21 @@ void DeviceScreener::postDevicesData(const QList<DeviceInfo> &devices) {
|
||||
|
||||
// локальный цикл — ждём, пока придёт finished()
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
|
||||
loop.quit();
|
||||
});
|
||||
loop.exec(); // <- здесь и запускается event-loop потока
|
||||
QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
// обрабатываем результат
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qWarning() << "Таймаут postDevicesData:" << m_urlAppUpdate;
|
||||
reply->abort();
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray raw = reply->readAll();
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(raw, &err);
|
||||
@ -103,20 +112,26 @@ void DeviceScreener::postDevicesData(const QList<DeviceInfo> &devices) {
|
||||
QJsonValue DeviceScreener::apiPost(const QString &path, const QJsonObject &body) {
|
||||
if (!m_manager) m_manager = new QNetworkAccessManager(this);
|
||||
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
QNetworkRequest request(QUrl(m_apiBase + path));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization",
|
||||
("Bearer " + settings.value("common/access_token").toString()).toUtf8());
|
||||
("Bearer " + SettingsDAO::get("access_token")).toUtf8());
|
||||
|
||||
QNetworkReply *reply = m_manager->post(request, QJsonDocument(body).toJson());
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { loop.quit(); });
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
QJsonValue result;
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qCritical() << "DeviceScreener apiPost timeout:" << path;
|
||||
reply->abort();
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
|
||||
if (doc.isObject()) {
|
||||
const QJsonObject root = doc.object();
|
||||
@ -133,27 +148,32 @@ QJsonValue DeviceScreener::apiPost(const QString &path, const QJsonObject &body)
|
||||
void DeviceScreener::apiPatch(const QString &path, const QJsonObject &body) {
|
||||
if (!m_manager) m_manager = new QNetworkAccessManager(this);
|
||||
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
QNetworkRequest request(QUrl(m_apiBase + path));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization",
|
||||
("Bearer " + settings.value("common/access_token").toString()).toUtf8());
|
||||
("Bearer " + SettingsDAO::get("access_token")).toUtf8());
|
||||
|
||||
QNetworkReply *reply = m_manager->sendCustomRequest(request, "PATCH", QJsonDocument(body).toJson());
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { loop.quit(); });
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qCritical() << "DeviceScreener apiPatch timeout:" << path;
|
||||
reply->abort();
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
qCritical() << "DeviceScreener apiPatch network error:" << reply->errorString();
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
QString DeviceScreener::createDeviceOnApi(const DeviceInfo &device) {
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const QString desktopId = settings.value("common/desktop_id").toString();
|
||||
const QString desktopId = SettingsDAO::get("desktop_id");
|
||||
if (desktopId.isEmpty()) {
|
||||
qWarning() << "createDeviceOnApi: desktop_id не задан";
|
||||
return {};
|
||||
@ -177,11 +197,10 @@ QString DeviceScreener::createDeviceOnApi(const DeviceInfo &device) {
|
||||
void DeviceScreener::postScreenshotToApi(const QString &apiId, const QByteArray &image) {
|
||||
if (!m_manager) m_manager = new QNetworkAccessManager(this);
|
||||
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const QString path = "/api/v1/device/add_screenshot/" + apiId;
|
||||
QNetworkRequest request(QUrl(m_apiBase + path));
|
||||
request.setRawHeader("Authorization",
|
||||
("Bearer " + settings.value("common/access_token").toString()).toUtf8());
|
||||
("Bearer " + SettingsDAO::get("access_token")).toUtf8());
|
||||
|
||||
auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
QHttpPart filePart;
|
||||
@ -194,11 +213,18 @@ void DeviceScreener::postScreenshotToApi(const QString &apiId, const QByteArray
|
||||
QNetworkReply *reply = m_manager->post(request, multiPart);
|
||||
multiPart->setParent(reply);
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { loop.quit(); });
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qWarning() << "postScreenshotToApi timeout for device" << apiId;
|
||||
reply->abort();
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "postScreenshotToApi network error:" << reply->errorString();
|
||||
} else {
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/EventDAO.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "db/EventInfo.h"
|
||||
|
||||
@ -39,7 +40,7 @@ NetworkService::NetworkService(QObject *parent) : QObject(parent) {
|
||||
m_pathMonitoringLog = settings.value("net/pathMonitoringLog", "/monitoring/log").toString();
|
||||
m_manager = new QNetworkAccessManager(this);
|
||||
m_token = settings.value("common/token").toString();
|
||||
m_accessToken = settings.value("common/access_token").toString();
|
||||
m_accessToken = SettingsDAO::get("access_token");
|
||||
}
|
||||
|
||||
NetworkService::~NetworkService() = default;
|
||||
@ -74,14 +75,21 @@ QJsonValue NetworkService::post(QHttpMultiPart *multiPart, const QString &path)
|
||||
multiPart->setParent(reply);
|
||||
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
|
||||
loop.quit();
|
||||
});
|
||||
loop.exec(); // <- здесь и запускается event-loop потока
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
QJsonValue result;
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qCritical() << "Таймаут запроса:" << path;
|
||||
reply->abort();
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
const QByteArray raw = reply->readAll();
|
||||
QJsonParseError err;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(raw, &err);
|
||||
@ -113,14 +121,21 @@ QJsonArray NetworkService::get(const QString &path, const QMap<QString, QString>
|
||||
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> guard(reply);
|
||||
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
|
||||
loop.quit();
|
||||
});
|
||||
loop.exec(); // <- здесь и запускается event-loop потока
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
QJsonArray result;
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qCritical() << "Таймаут запроса (GET):" << path;
|
||||
reply->abort();
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
const QByteArray raw = reply->readAll();
|
||||
QJsonParseError err;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(raw, &err);
|
||||
@ -205,14 +220,22 @@ QJsonValue NetworkService::postJson(const QString &path, const QJsonObject &body
|
||||
|
||||
QNetworkReply *reply = m_manager->post(request, QJsonDocument(body).toJson());
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
|
||||
loop.quit();
|
||||
});
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
QJsonValue result;
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qCritical() << "Таймаут запроса (postJson):" << path;
|
||||
reply->abort();
|
||||
result = QJsonValue::Undefined;
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
const QByteArray responseBody = reply->readAll();
|
||||
qCritical() << "Network error (POST):" << reply->errorString() << path
|
||||
<< "\n response body:" << responseBody;
|
||||
@ -236,8 +259,7 @@ QJsonValue NetworkService::postJson(const QString &path, const QJsonObject &body
|
||||
}
|
||||
|
||||
void NetworkService::loginAndSetup() {
|
||||
QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const QString apiKey = settings.value("common/api_key").toString();
|
||||
const QString apiKey = SettingsDAO::get("api_key");
|
||||
|
||||
// Шаг 1: логин
|
||||
QJsonObject loginBody;
|
||||
@ -257,13 +279,19 @@ void NetworkService::loginAndSetup() {
|
||||
|
||||
const QJsonObject loginData = loginResult.toObject();
|
||||
m_accessToken = loginData["access_token"].toString();
|
||||
settings.setValue("common/access_token", m_accessToken);
|
||||
settings.setValue("common/refresh_token", loginData["refresh_token"].toString());
|
||||
if (m_accessToken.isEmpty() || !loginData.contains("refresh_token") || !loginData.contains("expires_in")) {
|
||||
qCritical() << "loginAndSetup: ответ не содержит обязательных полей (access_token/refresh_token/expires_in)";
|
||||
emit finishedWithResult(-1);
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
SettingsDAO::set("access_token", m_accessToken);
|
||||
SettingsDAO::set("refresh_token", loginData["refresh_token"].toString());
|
||||
const qint64 expiresIn = static_cast<qint64>(loginData["expires_in"].toDouble());
|
||||
settings.setValue("common/token_expires_at", QDateTime::currentSecsSinceEpoch() + expiresIn);
|
||||
SettingsDAO::setLong("token_expires_at", QDateTime::currentSecsSinceEpoch() + expiresIn);
|
||||
|
||||
// Шаг 2: проверяем есть ли desktop_id
|
||||
const QString desktopId = settings.value("common/desktop_id").toString();
|
||||
const QString desktopId = SettingsDAO::get("desktop_id");
|
||||
if (!desktopId.isEmpty()) {
|
||||
emit finishedWithResult(1);
|
||||
emit finished();
|
||||
@ -275,6 +303,7 @@ void NetworkService::loginAndSetup() {
|
||||
desktopBody["blue_token"] = QJsonValue::Null;
|
||||
desktopBody["name"] = QJsonValue::Null;
|
||||
desktopBody["contact"] = QJsonValue::Null;
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
desktopBody["app_version"] = settings.value("common/version").toString();
|
||||
const QJsonValue desktopResult = postJson(m_apiBase + m_pathDesktopCreate, desktopBody, true);
|
||||
|
||||
@ -285,8 +314,8 @@ void NetworkService::loginAndSetup() {
|
||||
}
|
||||
|
||||
const QJsonObject desktopData = desktopResult.toObject();
|
||||
settings.setValue("common/desktop_id", desktopData["id"].toString());
|
||||
settings.setValue("common/profile_id", desktopData["profile_id"].toString());
|
||||
SettingsDAO::set("desktop_id", desktopData["id"].toString());
|
||||
SettingsDAO::set("profile_id", desktopData["profile_id"].toString());
|
||||
|
||||
emit finishedWithResult(1);
|
||||
emit finished();
|
||||
@ -350,12 +379,19 @@ QJsonValue NetworkService::patchJson(const QString &path, const QJsonObject &bod
|
||||
|
||||
QNetworkReply *reply = m_manager->sendCustomRequest(request, "PATCH", QJsonDocument(body).toJson());
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { loop.quit(); });
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
QJsonValue result;
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qCritical() << "Таймаут запроса (PATCH):" << path;
|
||||
reply->abort();
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
const QByteArray responseBody = reply->readAll();
|
||||
qCritical() << "Network error (PATCH):" << reply->errorString() << path
|
||||
<< "\n response body:" << responseBody;
|
||||
@ -487,13 +523,20 @@ void NetworkService::postDeviceScreenshot() {
|
||||
multiPart->setParent(reply);
|
||||
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
|
||||
loop.quit();
|
||||
});
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qWarning() << "[NetworkService] postDeviceScreenshot timeout for device" << device.apiId;
|
||||
reply->abort();
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "[NetworkService] postDeviceScreenshot failed:" << reply->errorString();
|
||||
} else {
|
||||
qDebug() << "[NetworkService] postDeviceScreenshot ok for device" << device.apiId;
|
||||
@ -503,8 +546,7 @@ void NetworkService::postDeviceScreenshot() {
|
||||
}
|
||||
|
||||
void NetworkService::createDevice() {
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const QString desktopId = settings.value("common/desktop_id").toString();
|
||||
const QString desktopId = SettingsDAO::get("desktop_id");
|
||||
if (desktopId.isEmpty()) {
|
||||
qWarning() << "createDevice: desktop_id not set";
|
||||
emit finished();
|
||||
@ -540,8 +582,7 @@ void NetworkService::updateDevice() {
|
||||
}
|
||||
|
||||
bool NetworkService::refreshToken() {
|
||||
QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const QString storedRefreshToken = settings.value("common/refresh_token").toString();
|
||||
const QString storedRefreshToken = SettingsDAO::get("refresh_token");
|
||||
if (storedRefreshToken.isEmpty()) {
|
||||
qWarning() << "refreshToken: refresh_token отсутствует";
|
||||
return false;
|
||||
@ -562,10 +603,14 @@ bool NetworkService::refreshToken() {
|
||||
|
||||
const QJsonObject data = result.toObject();
|
||||
m_accessToken = data["access_token"].toString();
|
||||
settings.setValue("common/access_token", m_accessToken);
|
||||
settings.setValue("common/refresh_token", data["refresh_token"].toString());
|
||||
if (m_accessToken.isEmpty() || !data.contains("refresh_token") || !data.contains("expires_in")) {
|
||||
qCritical() << "refreshToken: ответ не содержит обязательных полей";
|
||||
return false;
|
||||
}
|
||||
SettingsDAO::set("access_token", m_accessToken);
|
||||
SettingsDAO::set("refresh_token", data["refresh_token"].toString());
|
||||
const qint64 expiresIn = static_cast<qint64>(data["expires_in"].toDouble());
|
||||
settings.setValue("common/token_expires_at", QDateTime::currentSecsSinceEpoch() + expiresIn);
|
||||
SettingsDAO::setLong("token_expires_at", QDateTime::currentSecsSinceEpoch() + expiresIn);
|
||||
|
||||
m_tokenErrorShown = false; // сбрасываем если снова заработало
|
||||
qDebug() << "refreshToken: токен обновлён, истекает через" << expiresIn << "сек";
|
||||
@ -573,8 +618,7 @@ bool NetworkService::refreshToken() {
|
||||
}
|
||||
|
||||
bool NetworkService::ensureValidToken() {
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const qint64 expiresAt = settings.value("common/token_expires_at", 0).toLongLong();
|
||||
const qint64 expiresAt = SettingsDAO::getLong("token_expires_at");
|
||||
const qint64 now = QDateTime::currentSecsSinceEpoch();
|
||||
|
||||
// Обновляем заранее — за 30 минут до истечения
|
||||
@ -588,8 +632,7 @@ bool NetworkService::ensureValidToken() {
|
||||
void NetworkService::scheduleNextRefresh() {
|
||||
if (!m_running) return;
|
||||
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const qint64 expiresAt = settings.value("common/token_expires_at", 0).toLongLong();
|
||||
const qint64 expiresAt = SettingsDAO::getLong("token_expires_at");
|
||||
const qint64 now = QDateTime::currentSecsSinceEpoch();
|
||||
|
||||
// Обновляем за 30 минут до истечения; если уже пора — сразу
|
||||
@ -621,8 +664,7 @@ void NetworkService::getTasks() {
|
||||
return;
|
||||
}
|
||||
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const QString accessToken = settings.value("common/access_token").toString();
|
||||
const QString accessToken = SettingsDAO::get("access_token");
|
||||
|
||||
QUrl url(m_apiBase + m_pathGetTasks);
|
||||
QUrlQuery urlQuery;
|
||||
@ -702,10 +744,17 @@ void NetworkService::postMonitoringLog(const QString &type, const QString &event
|
||||
multiPart->setParent(reply);
|
||||
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (!timeout.isActive()) {
|
||||
qWarning() << "[NetworkService] postMonitoringLog timeout";
|
||||
reply->abort();
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "[NetworkService] postMonitoringLog failed:" << reply->errorString()
|
||||
<< "\n response body:" << reply->readAll();
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <QSettings>
|
||||
|
||||
#include "LoaderDialog.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
#include "net/NetworkService.h"
|
||||
|
||||
RegistrationWindow::RegistrationWindow(QWidget *parent): QWidget(parent) {
|
||||
@ -42,10 +43,7 @@ RegistrationWindow::RegistrationWindow(QWidget *parent): QWidget(parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
QSettings settings("config.ini", QSettings::IniFormat);
|
||||
settings.setValue("common/api_key", apiKey);
|
||||
}
|
||||
SettingsDAO::set("api_key", apiKey);
|
||||
|
||||
auto *loader = new LoaderDialog(this);
|
||||
loader->show();
|
||||
@ -67,8 +65,7 @@ RegistrationWindow::RegistrationWindow(QWidget *parent): QWidget(parent) {
|
||||
if (result == 1) {
|
||||
emit onRegisteredSuccess();
|
||||
} else if (result == 0) {
|
||||
QSettings s("config.ini", QSettings::IniFormat);
|
||||
s.remove("common/api_key");
|
||||
SettingsDAO::remove("api_key");
|
||||
QMessageBox::critical(this, "Ошибка входа", "Неверный API ключ. Проверьте и попробуйте снова.");
|
||||
} else {
|
||||
QMessageBox::critical(this, "Ошибка подключения",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user