#include "DeviceScreener.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "adb/AdbUtils.h" #include "dao/ApplicationDAO.h" DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) { const QSettings settings("config.ini", QSettings::IniFormat); m_urlAppUpdate = settings.value("net/appInfoUpdate").toString(); m_token = settings.value("common/token").toString(); } DeviceScreener::~DeviceScreener() = default; void DeviceScreener::postDevicesData(const QList &devices) { auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart jsonPart; jsonPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=UTF-8"); jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="metadata")"); QJsonArray list; for (const DeviceInfo &device: devices) { QJsonObject obj; obj["uniqueName"] = device.id; obj["status"] = device.status; obj["name"] = device.name; obj["screenWidth"] = device.screenWidth; obj["screenHeight"] = device.screenHeight; obj["battery"] = device.battery; list.append(obj); } jsonPart.setBody(QJsonDocument(list).toJson()); multiPart->append(jsonPart); for (const DeviceInfo &device: devices) { QHttpPart imagePart; QString imageHeader = QStringLiteral("form-data; name=files; filename=%1.jpg").arg(device.id); imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/jpeg"); imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, imageHeader.toUtf8()); imagePart.setBody(device.image); multiPart->append(imagePart); } QHttpPart uniquePart; uniquePart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="token")"); uniquePart.setBody(m_token.toUtf8()); multiPart->append(uniquePart); QNetworkAccessManager manager; // создаём внутри потока QUrl url(m_urlAppUpdate); QNetworkRequest request(url); // делаем запрос QNetworkReply *reply = manager.post(request, multiPart); multiPart->setParent(reply); // multiPart удалится, когда придёт ответ // локальный цикл — ждём, пока придёт finished() QEventLoop loop; QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); QObject::connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { loop.quit(); }); loop.exec(); // <- здесь и запускается event-loop потока // обрабатываем результат if (reply->error() == QNetworkReply::NoError) { QByteArray raw = reply->readAll(); QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(raw, &err); if (err.error == QJsonParseError::NoError && doc.isObject()) { QJsonObject root = doc.object(); if (!root.value("success").toBool()) { qWarning() << root; } } } else { qWarning() << "Response error:" << reply->errorString() << m_urlAppUpdate; } reply->deleteLater(); } void DeviceScreener::start() { QMap bankChecked; while (m_running) { QList > devices = readAdbDevices(); QStringList onlineIds; QList deviceInfos; for (const auto &[id, status]: devices) { DeviceInfo device; device.id = id; if (status == "device") { onlineIds << device.id; const bool isChecked = bankChecked.contains(id); device = readDeviceInfo(id, isChecked); device.image = takeScreenshot(id); if (!DeviceDAO::updateImage(id, device.image)) { qDebug() << "Cant update screenshot"; } bankChecked[id] = true; } device.status = status; device.updateTime = QDateTime::currentDateTime(); deviceInfos.append(device); if (!DeviceDAO::upsertDevice(device)) { qDebug() << "Cant update devive"; } } // if (!deviceInfos.empty()) { postDevicesData(deviceInfos); // } if (!DeviceDAO::markDevicesOfflineExcept(onlineIds)) { qDebug() << "Cant mark devices offline"; } QThread::msleep(1000); } emit finished(); } QByteArray convertPngToJpeg(const QByteArray &pngData, const int quality = 90) { QImage image; if (!image.loadFromData(pngData, "PNG")) { qWarning() << "Не удалось загрузить PNG из байт"; return {}; } QByteArray jpgData; QBuffer buffer(&jpgData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPG", quality); return jpgData; } QByteArray DeviceScreener::takeScreenshot(const QString &deviceId) { QProcess process; QStringList arguments; arguments << "-s" << deviceId << "exec-out" << "screencap" << "-p"; process.start("adb", arguments); if (!process.waitForFinished(5000)) { qWarning() << "ADB скриншот не завершился для" << deviceId; return {}; } const QByteArray imageData = process.readAllStandardOutput(); if (imageData.isEmpty()) { qWarning() << "Скриншот пустой для " << deviceId; return {}; } return convertPngToJpeg(imageData, 90); } QList > DeviceScreener::readAdbDevices() { QProcess process; process.start("adb", QStringList() << "devices"); process.waitForFinished(); const QString output = process.readAllStandardOutput(); QList > devices; const QStringList lines = output.split('\n'); for (int i = 1; i < lines.size(); ++i) { QString line = lines.at(i).trimmed(); if (line.isEmpty()) { continue; } /** * device — устройство готово к использованию. * offline — устройство подключено, но не может быть использовано. * unauthorized — устройство не авторизовано для работы с ADB (нужно подтвердить запрос на подключение на самом устройстве). * recovery — устройство в режиме восстановления. */ // Разделяем строку на id и type по табуляции if (QStringList parts = line.split('\t'); parts.size() == 2) { const QString &id = parts.at(0); const QString &status = parts.at(1); devices.append(qMakePair(id, status)); } } return devices; } static const QRegularExpression screenRegex(R"(Physical size: (\d+)x(\d+))"); static const QRegularExpression batteryRegex(R"(level:\s*(\d+))"); DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId, const bool isChecked) { DeviceInfo info; info.id = deviceId; info.screenWidth = 0; info.screenHeight = 0; info.battery = 0; auto runAdbCommand = [&](const QStringList &args) -> QString { QProcess process; process.start("adb", QStringList{"-s", deviceId} + args); process.waitForFinished(); return QString::fromUtf8(process.readAllStandardOutput()).trimmed(); }; info.name = runAdbCommand({"shell", "getprop", "ro.product.model"}); info.android = runAdbCommand({"shell", "getprop", "ro.build.version.release"}); // Размер экрана const QString screenOutput = runAdbCommand({"shell", "wm", "size"}); QRegularExpressionMatch match = screenRegex.match(screenOutput); if (match.hasMatch()) { info.screenWidth = match.captured(1).toInt(); info.screenHeight = match.captured(2).toInt(); } // Уровень батареи const QString batteryOutput = runAdbCommand({"shell", "dumpsys", "battery"}); match = batteryRegex.match(batteryOutput); if (match.hasMatch()) { info.battery = match.captured(1).toInt(); } if (!isChecked) { QSet apps = AdbUtils::getListApps(deviceId); if (!ApplicationDAO::checkInstalledApps(deviceId, apps)) { qWarning() << "Cant check installed apps"; } else { qDebug() << "Apps checked: " + deviceId; } } info.updateTime = QDateTime::currentDateTime(); return info; } void DeviceScreener::stop() { m_running = false; }