1
0
forked from BRT/arc

Integrate device data management with database access

Added SQLite database support for managing device information and common data. Implemented DAOs for handling device and common data operations, such as inserting/updating devices and tracking the last scan time. Enhanced `DeviceScreener` to read device info, update database records, and take screenshots.
This commit is contained in:
slava 2025-04-17 22:10:11 +07:00
parent 144a784166
commit 1672216bdc
11 changed files with 384 additions and 37 deletions

View File

@ -6,7 +6,7 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
if(WIN32)
if (WIN32)
# Укажи путь к Qt6
set(CMAKE_PREFIX_PATH "C:/Qt/6.9.0/msvc2022_arm64")
list(APPEND CMAKE_PREFIX_PATH "C:/Users/User/.vcpkg-clion/vcpkg/installed/arm64-windows")
@ -14,14 +14,22 @@ if(WIN32)
set(Tesseract_DIR "C:/Users/User/.vcpkg-clion/vcpkg/installed/arm64-windows/share/tesseract")
find_package(Tesseract CONFIG REQUIRED)
elseif(APPLE)
elseif (APPLE)
# Укажи путь к Qt6
set(CMAKE_PREFIX_PATH "/opt/homebrew/opt/qt")
include_directories("/opt/homebrew/opt/tesseract/include")
link_directories("/opt/homebrew/opt/tesseract/lib")
endif()
endif ()
find_package(Qt6 REQUIRED COMPONENTS Widgets)
find_package(Qt6 REQUIRED COMPONENTS
Widgets
Sql
)
file(GLOB_RECURSE DB_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/database/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/database/*.h"
)
file(GLOB_RECURSE VIEW_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/views/*.cpp"
@ -37,11 +45,13 @@ add_executable(ARCDesktopProject
main.cpp
${VIEW_SOURCES}
${SERVICES_SOURCES}
${DB_SOURCES}
)
if(WIN32)
if (WIN32)
target_link_libraries(ARCDesktopProject
Qt6::Widgets
Qt6::Sql
Tesseract::libtesseract
)
@ -49,9 +59,10 @@ if(WIN32)
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/images" DESTINATION "${CMAKE_BINARY_DIR}")
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}")
elseif(APPLE)
elseif (APPLE)
target_link_libraries(ARCDesktopProject
Qt6::Widgets
Qt6::Sql
tesseract
)
endif()
endif ()

BIN
database/app_data.db Normal file

Binary file not shown.

View File

@ -0,0 +1,47 @@
#include "common_data_dao.h"
#include "../database_manager.h"
#include <QSqlQuery>
#include <QSqlError>
QDateTime CommonDataDAO::getLastScan() {
QSqlQuery query(DatabaseManager::instance().database());
query.prepare("SELECT lastScan FROM common_data WHERE id = 1");
if (!query.exec()) {
qWarning() << "Ошибка при получении lastScan:" << query.lastError().text();
return {};
}
if (query.next()) {
const QString timestamp = query.value(0).toString();
return QDateTime::fromString(timestamp, Qt::ISODate);
}
return {};
}
bool CommonDataDAO::updateLastScan(const QDateTime& newTime) {
QSqlQuery query(DatabaseManager::instance().database());
query.prepare("UPDATE common_data SET lastScan = :time WHERE id = 1");
query.bindValue(":time", newTime.toString(Qt::ISODate));
if (!query.exec()) {
qWarning() << "Ошибка при обновлении lastScan:" << query.lastError().text();
return false;
}
if (query.numRowsAffected() == 0) {
QSqlQuery insertQuery(DatabaseManager::instance().database());
insertQuery.prepare("INSERT INTO common_data (id, lastScan) VALUES (1, :time)");
insertQuery.bindValue(":time", newTime.toString(Qt::ISODate));
if (!insertQuery.exec()) {
qWarning() << "Ошибка при INSERT lastScan:" << insertQuery.lastError().text();
return false;
}
}
return true;
}

View File

@ -0,0 +1,11 @@
#pragma once
#include <QString>
#include <QDateTime>
class CommonDataDAO {
public:
[[nodiscard]] static QDateTime getLastScan();
[[nodiscard]] static bool updateLastScan(const QDateTime &newTime);
};

View File

@ -0,0 +1,75 @@
#include "device_dao.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QVariant>
#include <QDebug>
#include "../database_manager.h"
bool DeviceDAO::upsertDevice(const DeviceInfo& device) {
QSqlQuery query(DatabaseManager::instance().database());
// Проверка: существует ли запись
query.prepare("SELECT COUNT(*) FROM devices WHERE id = :id");
query.bindValue(":id", device.id);
if (!query.exec() || !query.next()) {
qWarning() << "Ошибка при проверке существования записи:" << query.lastError().text();
return false;
}
bool exists = query.value(0).toInt() > 0;
if (exists) {
// Обновление
query.prepare(R"(
UPDATE devices SET
status = :status,
name = :name,
android = :android,
screen_width = :width,
screen_height = :height,
battery = :battery,
updateTime = :updateTime
WHERE id = :id
)");
} else {
// Вставка
query.prepare(R"(
INSERT INTO devices (
id, status, name, android, screen_width, screen_height, battery, updateTime
) VALUES (
:id, :status, :name, :android, :width, :height, :battery, :updateTime
)
)");
}
query.bindValue(":id", device.id);
query.bindValue(":status", device.status);
query.bindValue(":name", device.name);
query.bindValue(":android", device.android);
query.bindValue(":width", device.screenWidth);
query.bindValue(":height", device.screenHeight);
query.bindValue(":battery", device.battery);
query.bindValue(":updateTime", device.updateTime.toString(Qt::ISODate));
if (!query.exec()) {
qWarning() << "Ошибка при вставке/обновлении устройства:" << query.lastError().text();
return false;
}
return true;
}
bool DeviceDAO::updateImage(const QString& deviceId, const QByteArray& imageData) {
QSqlQuery query(DatabaseManager::instance().database());
query.prepare("UPDATE devices SET image = :image WHERE id = :id");
query.bindValue(":image", imageData);
query.bindValue(":id", deviceId);
if (!query.exec()) {
qWarning() << "Ошибка при обновлении изображения:" << query.lastError().text();
return false;
}
return true;
}

22
database/dao/device_dao.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <QDateTime>
#include <QSqlDatabase>
struct DeviceInfo {
QString id;
QString status;
QString name;
QString android;
int screenWidth;
int screenHeight;
int battery;
QDateTime updateTime;
};
class DeviceDAO {
public:
[[nodiscard]] static bool upsertDevice(const DeviceInfo &device);
[[nodiscard]] static bool updateImage(const QString &deviceId, const QByteArray &imageData);
};

View File

@ -0,0 +1,26 @@
#include "database_manager.h"
#include <QSqlError>
#include <QtCore/qcoreapplication.h>
DatabaseManager::DatabaseManager() {
db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(QCoreApplication::applicationDirPath() + "/database/app_data.db");
if (!db.open()) {
qWarning() << "Ошибка открытия базы данных:" << db.lastError().text();
}
}
DatabaseManager::~DatabaseManager() {
if (db.isOpen()) {
db.close();
}
}
DatabaseManager& DatabaseManager::instance() {
static DatabaseManager instance;
return instance;
}
QSqlDatabase& DatabaseManager::database() {
return db;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include <QSqlDatabase>
class DatabaseManager final : public QObject {
Q_OBJECT
public:
static DatabaseManager& instance();
QSqlDatabase& database();
private:
DatabaseManager();
~DatabaseManager() override;
QSqlDatabase db;
};

View File

@ -2,6 +2,9 @@
#include <QThread>
#include "views/MyWindow.h"
#include <tesseract/baseapi.h>
#include "database/database_manager.h"
#include "database/dao/common_data_dao.h"
#include "services/device_screener.h"
void setupWorkerAndThread(QCoreApplication& app) {
@ -42,26 +45,32 @@ int main(int argc, char *argv[]) {
// Setup worker and thread
setupWorkerAndThread(app);
const QDateTime current = QDateTime::currentDateTimeUtc();
qDebug() << "Обновление lastScan:" << CommonDataDAO::updateLastScan(current);
// tesseract, tesseract-lang
QImage image("./images/img.png");
if (image.isNull()) {
qDebug() << "Cant load the image";
return -1;
}
const QDateTime savedTime = CommonDataDAO::getLastScan();
qDebug() << "Сохранившееся время:" << savedTime.toString();
image = image.convertToFormat(QImage::Format_Grayscale8);
tesseract::TessBaseAPI tess;
if (tess.Init(nullptr, "rus")) {
qDebug() << "Ошибка инициализации Tesseract с русским языком....";
return -1;
}
tess.SetImage(image.bits(), image.width(), image.height(), 1, image.bytesPerLine());
QString result = QString::fromUtf8(tess.GetUTF8Text());
qDebug() << "Распознанный текст:" << result;
//
// // tesseract, tesseract-lang
// QImage image("./images/img.png");
// if (image.isNull()) {
// qDebug() << "Cant load the image";
// return -1;
// }
//
// image = image.convertToFormat(QImage::Format_Grayscale8);
//
// tesseract::TessBaseAPI tess;
// if (tess.Init(nullptr, "rus")) {
// qDebug() << "Ошибка инициализации Tesseract с русским языком....";
// return -1;
// }
//
// tess.SetImage(image.bits(), image.width(), image.height(), 1, image.bytesPerLine());
//
// QString result = QString::fromUtf8(tess.GetUTF8Text());
// qDebug() << "Распознанный текст:" << result;
MyWindow window;

View File

@ -1,20 +1,140 @@
#include "device_screener.h"
#include <QDebug>
#include <QThread>
DeviceScreener::DeviceScreener(QObject* parent) : QObject(parent) {}
#include <QRegularExpression>
#include <QProcess>
#include <QFile>
#include <QThread>
#include <../../database/dao/device_dao.h>
DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
}
DeviceScreener::~DeviceScreener() = default;
void DeviceScreener::start() {
qDebug() << "Worker started in thread:" << QThread::currentThread();
while (m_running) {
qDebug() << "Running in background...";
QThread::sleep(2);
QList<QPair<QString, QString>> devices = readAdbDevices();
for (const auto&[id, status] : devices) {
qDebug() << "Id:" << id << ", Status:" << status;
DeviceInfo device;
device.id = id;
device.status = status;
device.updateTime = QDateTime::currentDateTime();
if (status == "device") {
device = readDeviceInfo(id);
takeScreenshot(id);
}
qDebug() << "Update device: " << DeviceDAO::upsertDevice(device);
}
QThread::sleep(5);
}
emit finished();
}
bool 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 false;
}
const QByteArray imageData = process.readAllStandardOutput();
if (imageData.isEmpty()) {
qWarning() << "Скриншот пустой для " << deviceId;
return false;
}
// Обновляем изображение в БД
if (!DeviceDAO::updateImage(deviceId, imageData)) {
qWarning() << "Не удалось записать изображение в БД для" << deviceId;
return false;
}
qDebug() << "Screenshot saved, device: " << deviceId;
return true;
}
QList<QPair<QString, QString>> DeviceScreener::readAdbDevices() {
QProcess process;
process.start("adb", QStringList() << "devices");
process.waitForFinished();
const QString output = process.readAllStandardOutput();
// qDebug() << "[adb devices output]:\n" << output;
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;
}

View File

@ -1,19 +1,29 @@
#pragma once
#include <QObject>
#include <../../database/dao/device_dao.h>
class DeviceScreener final : public QObject {
Q_OBJECT
public:
explicit DeviceScreener(QObject* parent = nullptr);
explicit DeviceScreener(QObject *parent = nullptr);
~DeviceScreener() override;
public slots:
public slots:
void start();
void stop();
signals:
signals:
void finished();
private:
bool m_running = true;
static QList<QPair<QString, QString> > readAdbDevices();
static bool takeScreenshot(const QString &deviceId);
static DeviceInfo readDeviceInfo(const QString &deviceId);
};