#pragma once #include #include #include #include #include #include class DeviceScreener final : public QObject { Q_OBJECT public: explicit DeviceScreener(QObject *parent = nullptr); ~DeviceScreener() override; static constexpr qint64 kOfflineDebounceSecs = 30; // Как часто проверять/слать кадр истории экрана на сервер логов (с дедупом). static constexpr qint64 kMonitoringScreenIntervalSecs = 30; // Таймаут одной adb-команды в scanDeviceIo. На порядок выше нормального // времени (<1с), но ограничивает зависшую команду на флапающем устройстве, // чтобы один телефон не держал весь цикл опроса. static constexpr int kAdbCommandTimeoutMs = 10000; // Сколько устройств опрашивать по adb параллельно (фаза A). Ограничено, чтобы // не насыщать USB-шину одновременными screencap'ами. static constexpr int kMaxParallelScans = 6; // Как часто перечитывать список установленных приложений (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 m_lastApiUpdate; // deviceId → unix timestamp последнего PATCH QSet m_onlineDevices; // дебаунс-онлайн (true online после dedup) QMap m_offlineSince; // deviceId → когда пропал из ADB (для дебаунса) QMap m_lastMonitoringScreen; // deviceId → unix ts последней проверки кадра для истории экрана QMap m_lastSentScreenHash; // deviceId → sha256 последнего кадра, отправленного в историю экрана // Кэш неизменных свойств устройства (id/name/android/размер экрана) по adbSerial. // Эти поля не меняются за сессию, поэтому читаются по adb один раз, а не каждый // проход цикла. Каждый цикл обновляется только battery. Инвалидируется при уходе // устройства в OFFLINE (на случай переподключения другого аппарата по тому же serial). QMap m_staticInfoCache; QMap m_lastAppsCheck; // adbSerial → unix ts последнего getListApps // Результат чистого adb-опроса одного устройства (фаза A, выполняется в пуле // потоков). Не содержит ничего, что требует БД/членов класса. struct ScanResult { QString adbSerial; DeviceInfo info; // идентичность + battery + размер экрана bool identified = false; // false → android_id не прочитался, пропустить цикл QByteArray image; // JPEG скриншот (может быть пустым) QSet apps; // установленные пакеты (валидно только при appsRead) bool appsRead = false; // true → getListApps вызывался в этом цикле }; static QList > readAdbDevices(); static QByteArray takeScreenshot(const QString &deviceId); // Чистый adb-I/O одного устройства: потокобезопасно (не трогает БД и члены). // cachedStatic — снимок ранее прочитанной статики (пустой id → читаем с нуля). // readApps — перечитывать ли список установленных приложений в этом цикле. static ScanResult scanDeviceIo(const QString &adbSerial, const DeviceInfo &cachedStatic, bool readApps); void postDevicesData(const QList &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); };