diff --git a/android/adb/AdbUtils.cpp b/android/adb/AdbUtils.cpp index c73c8b9..45b7283 100644 --- a/android/adb/AdbUtils.cpp +++ b/android/adb/AdbUtils.cpp @@ -234,22 +234,18 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, int idx, bool scre // 4. Remove temp XML file startProcess(adb, {"-s", deviceId, "shell", "rm", "/sdcard/uidump.xml"}); - // 5. Проверяем что XML не пустой, достаточного размера и содержит непустые UI-элементы + // 5. Минимальная санитарная проверка: дамп не пустой, достаточного размера + // и содержит хотя бы одну ноду с непустым text или content-desc. + // Этого хватает, чтобы отсеять WebView-shell-only состояния Ozon + // (там в шелле labeled нод вообще нет), и при этом не отбрасывать + // экраны RN/Compose (Dushanbe, Black), где интерактив строится на + // LinearLayout/View с content-desc, без Button/text/EditText. static const QRegularExpression reNonEmpty(R"((text|content-desc)="[^"]+")"); const bool hasContent = reNonEmpty.match(xmlOut).hasMatch(); - // Дополнительно: ловим "WebView-shell-only" состояние, когда AT отдаёт - // только обёртку WebView и нижнюю нав-панель Ozon (FINANCE_TAB*), - // но Svelte-контент ещё не отрисован. Признак полезного среза — - // хотя бы один Button с непустым text ИЛИ EditText. Нав-табы у Ozon - // имеют пустой text (только content-desc), поэтому регекс их не зачтёт. - static const QRegularExpression reUsefulUi( - R"(class="android\.widget\.Button"[^/]*text="[^"]+"|text="[^"]+"[^/]*class="android\.widget\.Button"|class="android\.widget\.EditText")" - ); - const bool hasUsefulUi = reUsefulUi.match(xmlOut).hasMatch(); if (xmlOut.trimmed().isEmpty() || !xmlOut.contains("pending; + connect(reply, &QNetworkReply::finished, reply, [reply, manager, tally, label]() { + if (reply->error() != QNetworkReply::NoError) { + tally->errors << QString("%1: %2").arg(label, reply->errorString()); + } else { + // Telegram возвращает 200 + JSON {"ok":false,...} даже на ошибки уровня API + const QByteArray body = reply->readAll(); + if (!body.contains("\"ok\":true")) { + tally->errors << QString("%1: API rejected (%2)") + .arg(label, QString::fromUtf8(body.left(200))); + } + } + reply->deleteLater(); + if (--tally->pending == 0) { + if (tally->errors.isEmpty()) { + QMessageBox::information(nullptr, QObject::tr("Готово"), + QObject::tr("Дамп отправлен в Telegram")); + } else { + QMessageBox::warning(nullptr, QObject::tr("Ошибка отправки"), + QObject::tr("Не удалось отправить часть данных:\n%1") + .arg(tally->errors.join("\n"))); + } + delete tally; + manager->deleteLater(); + } + }); + }; + // 1. Отправляем скриншот через sendPhoto if (!screenshot.isEmpty()) { auto *mp = new QHttpMultiPart(QHttpMultiPart::FormDataType); @@ -440,7 +476,7 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p req.setSslConfiguration(QSslConfiguration::defaultConfiguration()); QNetworkReply *reply = manager->post(req, mp); mp->setParent(reply); - connect(reply, &QNetworkReply::finished, reply, &QObject::deleteLater); + track(reply, "sendPhoto"); } // 2. Отправляем XML-дамп как документ @@ -469,14 +505,16 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p req.setSslConfiguration(QSslConfiguration::defaultConfiguration()); QNetworkReply *reply = manager->post(req, mp); mp->setParent(reply); - connect(reply, &QNetworkReply::finished, reply, &QObject::deleteLater); + track(reply, "sendDocument(xml)"); } - // Удаляем manager после всех отправок - QTimer::singleShot(30000, manager, &QObject::deleteLater); - - QMessageBox::information(nullptr, tr("Готово"), - tr("Дамп отправлен в Telegram")); + if (tally->pending == 0) { + // Ничего не запустили — освободим ресурсы + delete tally; + manager->deleteLater(); + QMessageBox::warning(nullptr, tr("Ошибка"), + tr("Нет данных для отправки")); + } }); topRightLayout->addWidget(settingsBtn);