Introduced a new `NetworkService` for posting account and device data efficiently. Improved the transaction UI with styled tables and added lazy content loading. Changed screenshot handling to return JPEG data directly, and restructured device processing for better error handling and data integration.
238 lines
8.1 KiB
C++
238 lines
8.1 KiB
C++
#include "DeviceScreener.h"
|
||
|
||
#include <QRegularExpression>
|
||
#include <QProcess>
|
||
#include <QFile>
|
||
#include <QBuffer>
|
||
#include <qeventloop.h>
|
||
#include <QThread>
|
||
#include <QImage>
|
||
#include <QHttpMultiPart>
|
||
#include <QJsonObject>
|
||
#include <QJsonArray>
|
||
#include <QNetworkReply>
|
||
#include <QNetworkRequest>
|
||
#include <dao/DeviceDAO.h>
|
||
#include <db/DeviceInfo.h>
|
||
|
||
DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
|
||
}
|
||
|
||
DeviceScreener::~DeviceScreener() = default;
|
||
|
||
void postDevicesData(const QList<DeviceInfo> &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="uniqueName")");
|
||
uniquePart.setBody("DT88bokcwQ");
|
||
|
||
multiPart->append(uniquePart);
|
||
|
||
QNetworkAccessManager manager; // создаём внутри потока
|
||
QUrl url("http://localhost:9999/api/v1/app/update");
|
||
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();
|
||
qDebug() << "Успешный ответ: " << root.value("success").toBool();
|
||
}
|
||
} else {
|
||
qWarning() << "Ошибка:" << reply->errorString();
|
||
}
|
||
reply->deleteLater();
|
||
}
|
||
|
||
void DeviceScreener::start() {
|
||
while (m_running) {
|
||
QList<QPair<QString, QString> > devices = readAdbDevices();
|
||
QStringList onlineIds;
|
||
QList<DeviceInfo> deviceInfos;
|
||
for (const auto &[id, status]: devices) {
|
||
DeviceInfo device;
|
||
device.id = id;
|
||
|
||
|
||
if (status == "device") {
|
||
onlineIds << device.id;
|
||
device = readDeviceInfo(id);
|
||
device.image = takeScreenshot(id);
|
||
if (!DeviceDAO::updateImage(id, device.image)) {
|
||
qDebug() << "Cant update screenshot";
|
||
}
|
||
}
|
||
device.status = status;
|
||
device.updateTime = QDateTime::currentDateTime();
|
||
deviceInfos.append(device);
|
||
if (!DeviceDAO::upsertDevice(device)) {
|
||
qDebug() << "Cant update devive";
|
||
}
|
||
}
|
||
|
||
postDevicesData(deviceInfos);
|
||
|
||
if (!DeviceDAO::markDevicesOfflineExcept(onlineIds)) {
|
||
qDebug() << "Cant mark devices offline";
|
||
}
|
||
QThread::sleep(3);
|
||
}
|
||
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<QPair<QString, QString> > DeviceScreener::readAdbDevices() {
|
||
QProcess process;
|
||
process.start("adb", QStringList() << "devices");
|
||
process.waitForFinished();
|
||
|
||
const QString output = process.readAllStandardOutput();
|
||
QList<QPair<QString, QString> > 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) {
|
||
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();
|
||
}
|
||
|
||
info.updateTime = QDateTime::currentDateTime();
|
||
return info;
|
||
}
|
||
|
||
|
||
void DeviceScreener::stop() {
|
||
m_running = false;
|
||
}
|