1
0
forked from BRT/arc
arc/services/DeviceScreener.h

134 lines
9.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <QObject>
#include <QNetworkAccessManager>
#include <QSet>
#include <QMap>
#include <dao/DeviceDAO.h>
#include <db/DeviceInfo.h>
class DeviceScreener final : public QObject {
Q_OBJECT
public:
explicit DeviceScreener(QObject *parent = nullptr);
~DeviceScreener() override;
static constexpr qint64 kOfflineDebounceSecs = 30;
// Как часто проверять/слать кадр истории экрана на сервер логов (с дедупом).
static constexpr qint64 kMonitoringScreenIntervalSecs = 120;
// Таймаут одной adb-команды в scanDeviceIo. На порядок выше нормального
// времени (<1с), но ограничивает зависшую команду на флапающем устройстве,
// чтобы один телефон не держал весь цикл опроса.
static constexpr int kAdbCommandTimeoutMs = 10000;
// Сколько устройств опрашивать по adb параллельно (фаза A). Ограничено, чтобы
// не насыщать USB-шину одновременными screencap'ами.
static constexpr int kMaxParallelScans = 6;
// Как часто снимать превью-кадр устройства (screencap). Раньше кадр снимался
// КАЖДЫЙ цикл (~1-2с на устройство) и при ~10 девайсах насыщал общий adb-сервер,
// конкурируя с uiautomator-дампами рабочих скриптов (FETCH_TRANSACTIONS) и
// приближая их к 10-минутному watchdog'у. Превью в UI и истории экрана так часто
// не нужно (отправка в мониторинг и так троттлится kMonitoringScreenIntervalSecs),
// поэтому захват ограничиваем. Online/offline-детект от кадра не зависит.
static constexpr qint64 kScreencapIntervalSecs = 4;
// Как часто перечитывать список установленных приложений (getListApps). Список
// меняется редко, поэтому не дёргаем adb каждый цикл.
static constexpr qint64 kAppsCheckIntervalSecs = 60;
public slots:
void start();
void stop();
signals:
void finished();
void firstPassDone();
void deviceWentOnline(const QString &deviceId);
void deviceWentOffline(const QString &deviceId);
private:
bool m_running = true;
bool m_firstScan = true;
QString m_urlAppUpdate;
QString m_token;
QString m_apiBase;
QNetworkAccessManager *m_manager = nullptr;
QMap<QString, qint64> m_lastApiUpdate; // deviceId → unix timestamp последнего PATCH
QSet<QString> m_onlineDevices; // дебаунс-онлайн (true online после dedup)
QMap<QString, qint64> m_offlineSince; // deviceId → когда пропал из ADB (для дебаунса)
QMap<QString, qint64> m_lastMonitoringScreen; // deviceId → unix ts последней проверки кадра для истории экрана
QMap<QString, QByteArray> m_lastSentScreenHash; // deviceId → sha256 последнего кадра, отправленного в историю экрана
// Кэш неизменных свойств устройства (id/name/android/размер экрана) по adbSerial.
// Эти поля не меняются за сессию, поэтому читаются по adb один раз, а не каждый
// проход цикла. Каждый цикл обновляется только battery. Инвалидируется при уходе
// устройства в OFFLINE (на случай переподключения другого аппарата по тому же serial).
QMap<QString, DeviceInfo> m_staticInfoCache;
QMap<QString, qint64> m_lastAppsCheck; // adbSerial → unix ts последнего getListApps
QMap<QString, qint64> m_lastScreencap; // adbSerial → unix ts последнего screencap (троттл превью)
// --- ADB через Wi-Fi (USB — основной транспорт, Wi-Fi — горячий резерв) ---
bool m_wifiEnabled = true; // [wifi] enabled
int m_wifiPort = 5555; // [wifi] port (tcpip)
qint64 m_wifiReconnectIntervalSecs = 30; // [wifi] reconnectIntervalSecs
qint64 m_wifiBootstrapRetrySecs = 120; // [wifi] bootstrapRetryIntervalSecs
qint64 m_lastWifiReconnect = 0; // unix ts последнего reconnectKnown
QMap<QString, qint64> m_lastWifiBootstrap; // android_id → unix ts последней попытки bootstrap
bool m_wifiMacRediscover = true; // [wifi] macRediscoverEnabled (искать новый IP по MAC)
qint64 m_wifiMacRediscoverIntervalSecs = 300; // [wifi] macRediscoverIntervalSecs (троттл поиска по MAC)
bool m_wifiSubnetSweep = false; // [wifi] subnetSweepEnabled (ping-скан подсети для ARP)
qint64 m_lastMacRediscover = 0; // unix ts последнего поиска по MAC
// Результат чистого adb-опроса одного устройства (фаза A, выполняется в пуле
// потоков). Не содержит ничего, что требует БД/членов класса.
struct ScanResult {
QString adbSerial;
DeviceInfo info; // идентичность + battery + размер экрана
bool identified = false; // false → android_id не прочитался, пропустить цикл
QByteArray image; // JPEG скриншот (может быть пустым)
QSet<QString> apps; // установленные пакеты (валидно только при appsRead)
bool appsRead = false; // true → getListApps вызывался в этом цикле
};
// adbOk (если передан) выставляется в false, когда сам `adb devices` не
// отработал (не стартовал / таймаут) — в этом случае возвращается пустой
// список, но цикл НЕ должен трактовать это как «все устройства пропали».
static QList<QPair<QString, QString> > readAdbDevices(bool *adbOk = nullptr);
static QByteArray takeScreenshot(const QString &deviceId);
// Чистый adb-I/O одного устройства: потокобезопасно (не трогает БД и члены).
// cachedStatic — снимок ранее прочитанной статики (пустой id → читаем с нуля).
// readApps — перечитывать ли список установленных приложений в этом цикле.
// takeShot — снимать ли превью-кадр (screencap) в этом цикле (троттл, см.
// kScreencapIntervalSecs). false → result.image пустой, превью не обновляется.
static ScanResult scanDeviceIo(const QString &adbSerial, const DeviceInfo &cachedStatic, bool readApps, bool takeShot);
void postDevicesData(const QList<DeviceInfo> &devices);
QJsonValue apiGet(const QString &path);
QJsonValue apiPost(const QString &path, const QJsonObject &body);
QString createDeviceOnApi(const DeviceInfo &device);
// Строит тело PATCH (с поиском bank_profile_id по БД — вызывать на главном
// потоке) и отправляет статус + опциональный скриншот fire-and-forget через
// dispatchDeviceSyncToApi, чтобы сетевой таймаут не тормозил цикл опроса.
void updateDeviceOnApi(const DeviceInfo &device, const QByteArray &image = {});
// Fire-and-forget отправка кадра истории экрана на сервер логов (monitoringBase).
// Создаёт временный NetworkService в отдельном потоке (паттерн LogArchiveSender).
static void dispatchScreenshotToMonitoring(const QString &desktopId,
const QString &deviceId,
const QString &name,
const QByteArray &jpeg);
// Fire-and-forget синхронизация устройства с основным API: PATCH статуса и
// (если image непустой) POST скриншота. Поднимает свой поток + QNetworkAccessManager,
// чтобы 30-секундный таймаут не блокировал цикл опроса. Всё необходимое
// (токен, тело запроса) передаётся по значению — БД здесь не трогается.
static void dispatchDeviceSyncToApi(const QString &apiBase,
const QString &accessToken,
const QString &apiId,
const QJsonObject &patchBody,
const QByteArray &image);
};