Update error reporting functions to include event ID and improve scroll detection in transaction scripts. Adjust network ping interval to 15 seconds.
This commit is contained in:
parent
6d60cecef4
commit
569eefb3e8
@ -519,6 +519,14 @@ GetLastTransactionsScript::SearchResult GetLastTransactionsScript::searchOne(
|
|||||||
Node node;
|
Node node;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Guard от «залипшей» прокрутки: если свайп не двигает список (банковское
|
||||||
|
// приложение перестало скроллиться — частый случай при сбоях UI/жестов),
|
||||||
|
// видимые транзакции не меняются. Тогда листать до maxScrolls бессмысленно,
|
||||||
|
// а при десятках целей это упирается в 10-минутный watchdog → "Interrupted".
|
||||||
|
// Детектируем по сигнатуре видимого набора: 2 одинаковых подряд = прогресса нет.
|
||||||
|
QString prevViewSig;
|
||||||
|
int stallScrolls = 0;
|
||||||
|
|
||||||
for (int scroll = 0; scroll <= maxScrolls; ++scroll) {
|
for (int scroll = 0; scroll <= maxScrolls; ++scroll) {
|
||||||
if (interrupted()) {
|
if (interrupted()) {
|
||||||
m_error = "Interrupted";
|
m_error = "Interrupted";
|
||||||
@ -1012,6 +1020,27 @@ GetLastTransactionsScript::SearchResult GetLastTransactionsScript::searchOne(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Детект отсутствия прогресса прокрутки: сравниваем сигнатуру текущего
|
||||||
|
// видимого набора с предыдущей. Совпала — свайп ничего не сдвинул.
|
||||||
|
{
|
||||||
|
QStringList sigParts;
|
||||||
|
for (const VypiskaItem &it : items) {
|
||||||
|
sigParts << QString::number(it.amount, 'f', 2) + "|" + it.date + "|" + it.time;
|
||||||
|
}
|
||||||
|
const QString viewSig = sigParts.join(';') + "##" + groupDates.join(',');
|
||||||
|
if (!prevViewSig.isEmpty() && viewSig == prevViewSig) {
|
||||||
|
if (++stallScrolls >= 2) {
|
||||||
|
qWarning() << "[Dushanbe::GetLastTransactions] No scroll progress for"
|
||||||
|
<< stallScrolls << "swipes (list stuck) — aborting search for amount="
|
||||||
|
<< QString::number(searchAmount, 'f', 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stallScrolls = 0;
|
||||||
|
}
|
||||||
|
prevViewSig = viewSig;
|
||||||
|
}
|
||||||
|
|
||||||
// Скроллим вниз — свайп внутри области транзакций
|
// Скроллим вниз — свайп внутри области транзакций
|
||||||
if (scroll < maxScrolls) {
|
if (scroll < maxScrolls) {
|
||||||
if (interrupted()) { m_error = "Interrupted"; return SearchResult::NotFound; }
|
if (interrupted()) { m_error = "Interrupted"; return SearchResult::NotFound; }
|
||||||
|
|||||||
@ -164,25 +164,45 @@ Node ScreenXmlParser::findPinCodeEditText() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ScreenXmlParser::isHomeScreen() {
|
bool ScreenXmlParser::isHomeScreen() {
|
||||||
|
// Нижняя навигация (Главная + соседняя вкладка) присутствует на ВСЕХ
|
||||||
|
// основных вкладках — Главная, История, Переводы, MiniApps — поэтому одного
|
||||||
|
// таб-бара недостаточно: экран «История/Операции» иначе ошибочно
|
||||||
|
// принимается за home, и скрипт пытается открыть боковое меню/листать не с
|
||||||
|
// той страницы (см. «Side menu not detected»). Требуем дополнительно
|
||||||
|
// уникальный для главной якорь и явно отсекаем экран истории.
|
||||||
bool hasGlavnaya = false;
|
bool hasGlavnaya = false;
|
||||||
bool hasBottomNav = false;
|
bool hasBottomNav = false;
|
||||||
|
bool hasHomeAnchor = false;
|
||||||
|
bool isHistoryScreen = false;
|
||||||
for (const Node &node : m_nodes) {
|
for (const Node &node : m_nodes) {
|
||||||
if (node.contentDesc.contains(QString::fromUtf8("Главная"))) {
|
const QString &d = node.contentDesc;
|
||||||
|
if (d.contains(QString::fromUtf8("Главная"))) {
|
||||||
hasGlavnaya = true;
|
hasGlavnaya = true;
|
||||||
}
|
}
|
||||||
// Нижняя навигация — любая из соседних вкладок указывает, что мы на
|
// Нижняя навигация — любая из соседних вкладок указывает, что мы на
|
||||||
// основной странице приложения, а не на splash/PIN/диалоге.
|
// основной странице приложения, а не на splash/PIN/диалоге.
|
||||||
if (node.contentDesc == QString::fromUtf8("Переводы") ||
|
if (d == QString::fromUtf8("Переводы") ||
|
||||||
node.contentDesc == QString::fromUtf8("Сервисы") ||
|
d == QString::fromUtf8("Сервисы") ||
|
||||||
node.contentDesc == QString::fromUtf8("MiniApps") ||
|
d == QString::fromUtf8("MiniApps") ||
|
||||||
node.contentDesc == QString::fromUtf8("История")) {
|
d == QString::fromUtf8("История")) {
|
||||||
hasBottomNav = true;
|
hasBottomNav = true;
|
||||||
}
|
}
|
||||||
if (hasGlavnaya && hasBottomNav) {
|
// Уникальные для главного экрана элементы (нет на Истории/Переводах):
|
||||||
return true;
|
// заголовок «Мини приложения», плитки переводов «DC по номеру …»
|
||||||
|
// (в 3.2.38 — «DC (по номеру …)») и карточка баланса «TJS\n<дата>».
|
||||||
|
if (d == QString::fromUtf8("Мини приложения") ||
|
||||||
|
d.startsWith(QString::fromUtf8("DC по номеру")) ||
|
||||||
|
d.startsWith(QString::fromUtf8("DC (по номеру")) ||
|
||||||
|
(d.startsWith(QStringLiteral("TJS")) && d.contains('\n'))) {
|
||||||
|
hasHomeAnchor = true;
|
||||||
|
}
|
||||||
|
// Маркеры экрана «История/Операции» — на главном их нет.
|
||||||
|
if (d == QString::fromUtf8("Выписка") ||
|
||||||
|
d == QString::fromUtf8("Операции")) {
|
||||||
|
isHistoryScreen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return hasGlavnaya && hasBottomNav && hasHomeAnchor && !isHistoryScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<double> ScreenXmlParser::parseBalanceFromHomeScreenOpt() {
|
std::optional<double> ScreenXmlParser::parseBalanceFromHomeScreenOpt() {
|
||||||
|
|||||||
@ -78,7 +78,7 @@ void EventHandler::start() {
|
|||||||
if (e.sentToServer) continue;
|
if (e.sentToServer) continue;
|
||||||
EventDAO::updateEvent(e.id, EventStatus::Error, "App restarted");
|
EventDAO::updateEvent(e.id, EventStatus::Error, "App restarted");
|
||||||
EventDAO::markSentToServer(e.id);
|
EventDAO::markSentToServer(e.id);
|
||||||
sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName, "App restarted, event was pending", {}, -1, e.transactionId, e.accountId);
|
sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName, "App restarted, event was pending", {}, e.id, e.transactionId, e.accountId);
|
||||||
qDebug() << "[EventHandler] Closed stale event id:" << e.id;
|
qDebug() << "[EventHandler] Closed stale event id:" << e.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,7 +127,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
|||||||
if (eventType == EventType::FetchProfile) {
|
if (eventType == EventType::FetchProfile) {
|
||||||
const QList<EventInfo> cancelled = EventDAO::cancelWaitingByMaterial(materialId, EventType::FetchProfile);
|
const QList<EventInfo> cancelled = EventDAO::cancelWaitingByMaterial(materialId, EventType::FetchProfile);
|
||||||
for (const EventInfo &c : cancelled) {
|
for (const EventInfo &c : cancelled) {
|
||||||
sendTaskError(eventTypeToString(c.type), c.externalId, c.bankName, "Cancelled: replaced by new task", {}, -1, c.transactionId, c.accountId);
|
sendTaskError(eventTypeToString(c.type), c.externalId, c.bankName, "Cancelled: replaced by new task", {}, c.id, c.transactionId, c.accountId);
|
||||||
EventDAO::markSentToServer(c.id);
|
EventDAO::markSentToServer(c.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
|||||||
if (!onlineDevices.contains(account.deviceId)) {
|
if (!onlineDevices.contains(account.deviceId)) {
|
||||||
qWarning() << "[EventHandler] Device offline for material_id:" << materialId << "device:" << account.deviceId;
|
qWarning() << "[EventHandler] Device offline for material_id:" << materialId << "device:" << account.deviceId;
|
||||||
EventDAO::updateEvent(eventId, EventStatus::Error, "Device is offline");
|
EventDAO::updateEvent(eventId, EventStatus::Error, "Device is offline");
|
||||||
sendTaskError(typeStr, materialId, bankName, "Device is offline", {}, -1, incomingTransactionId, account.id);
|
sendTaskError(typeStr, materialId, bankName, "Device is offline", {}, eventId, incomingTransactionId, account.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +417,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
declined["status"] = QStringLiteral("declined");
|
declined["status"] = QStringLiteral("declined");
|
||||||
declined["extra"] = QJsonObject();
|
declined["extra"] = QJsonObject();
|
||||||
declined["created_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
declined["created_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||||
sendTaskResult(QStringLiteral("CREATE_TRANSACTION"), event.externalId, event.bankName, declined, -1);
|
sendTaskResult(QStringLiteral("CREATE_TRANSACTION"), event.externalId, event.bankName, declined, event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventDAO::markSentToServer(event.id);
|
EventDAO::markSentToServer(event.id);
|
||||||
@ -774,7 +774,7 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn
|
|||||||
const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout")
|
const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout")
|
||||||
.arg(eventTypeToString(event.type), event.bankName, event.externalId);
|
.arg(eventTypeToString(event.type), event.bankName, event.externalId);
|
||||||
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg);
|
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg);
|
||||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, timeoutMsg, {}, -1, event.transactionId, event.accountId);
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, timeoutMsg, {}, event.id, event.transactionId, event.accountId);
|
||||||
EventDAO::markSentToServer(event.id);
|
EventDAO::markSentToServer(event.id);
|
||||||
|
|
||||||
// Убиваем банковское приложение на устройстве после таймаута
|
// Убиваем банковское приложение на устройстве после таймаута
|
||||||
@ -811,7 +811,7 @@ void EventHandler::processNextTask(const QString &deviceId) {
|
|||||||
if (waitSecs > 5 * 60) {
|
if (waitSecs > 5 * 60) {
|
||||||
qWarning() << "[EventHandler] Task expired after" << waitSecs << "s, event id:" << event.id;
|
qWarning() << "[EventHandler] Task expired after" << waitSecs << "s, event id:" << event.id;
|
||||||
EventDAO::updateEvent(event.id, EventStatus::Error, "Task expired: waited " + QString::number(waitSecs) + "s");
|
EventDAO::updateEvent(event.id, EventStatus::Error, "Task expired: waited " + QString::number(waitSecs) + "s");
|
||||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Task expired: not started within 5 minutes", {}, -1, event.transactionId, event.accountId);
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Task expired: not started within 5 minutes", {}, event.id, event.transactionId, event.accountId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -821,7 +821,7 @@ void EventHandler::processNextTask(const QString &deviceId) {
|
|||||||
qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId;
|
qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId;
|
||||||
EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled");
|
EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled");
|
||||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName,
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName,
|
||||||
"Bank profile is disabled: " + account.appCode, {}, -1, event.transactionId, event.accountId);
|
"Bank profile is disabled: " + account.appCode, {}, event.id, event.transactionId, event.accountId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -194,7 +194,7 @@ private:
|
|||||||
bool m_unhealthyEmitted = false; // чтобы не спамить syncHealthChanged
|
bool m_unhealthyEmitted = false; // чтобы не спамить syncHealthChanged
|
||||||
static constexpr int kSyncFailureThreshold = 3;
|
static constexpr int kSyncFailureThreshold = 3;
|
||||||
static constexpr int kPeriodicSyncMs = 60 * 1000;
|
static constexpr int kPeriodicSyncMs = 60 * 1000;
|
||||||
static constexpr int kPingProfileMs = 5 * 1000;
|
static constexpr int kPingProfileMs = 15 * 1000;
|
||||||
|
|
||||||
// Heartbeat (ping)
|
// Heartbeat (ping)
|
||||||
bool m_pingEnabled = false;
|
bool m_pingEnabled = false;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user