diff --git a/CMakeLists.txt b/CMakeLists.txt index f759206..08aed74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() \ No newline at end of file +endif () \ No newline at end of file diff --git a/database/app_data.db b/database/app_data.db new file mode 100644 index 0000000..d5b3627 Binary files /dev/null and b/database/app_data.db differ diff --git a/database/dao/common_data_dao.cpp b/database/dao/common_data_dao.cpp new file mode 100644 index 0000000..ccb20e7 --- /dev/null +++ b/database/dao/common_data_dao.cpp @@ -0,0 +1,47 @@ +#include "common_data_dao.h" +#include "../database_manager.h" + +#include +#include + + +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; +} diff --git a/database/dao/common_data_dao.h b/database/dao/common_data_dao.h new file mode 100644 index 0000000..faccd5f --- /dev/null +++ b/database/dao/common_data_dao.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class CommonDataDAO { +public: + [[nodiscard]] static QDateTime getLastScan(); + + [[nodiscard]] static bool updateLastScan(const QDateTime &newTime); +}; diff --git a/database/dao/device_dao.cpp b/database/dao/device_dao.cpp new file mode 100644 index 0000000..32368eb --- /dev/null +++ b/database/dao/device_dao.cpp @@ -0,0 +1,75 @@ +#include "device_dao.h" +#include +#include +#include +#include +#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; +} diff --git a/database/dao/device_dao.h b/database/dao/device_dao.h new file mode 100644 index 0000000..8a9060f --- /dev/null +++ b/database/dao/device_dao.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +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); +}; diff --git a/database/database_manager.cpp b/database/database_manager.cpp new file mode 100644 index 0000000..d49dfe8 --- /dev/null +++ b/database/database_manager.cpp @@ -0,0 +1,26 @@ +#include "database_manager.h" +#include +#include + +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; +} \ No newline at end of file diff --git a/database/database_manager.h b/database/database_manager.h new file mode 100644 index 0000000..7321c17 --- /dev/null +++ b/database/database_manager.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class DatabaseManager final : public QObject { + Q_OBJECT +public: + static DatabaseManager& instance(); + + QSqlDatabase& database(); + +private: + DatabaseManager(); + ~DatabaseManager() override; + QSqlDatabase db; +}; \ No newline at end of file diff --git a/main.cpp b/main.cpp index 44c7d28..478db6e 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,9 @@ #include #include "views/MyWindow.h" #include + +#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; diff --git a/services/device_screener.cpp b/services/device_screener.cpp index 8e14724..6927846 100644 --- a/services/device_screener.cpp +++ b/services/device_screener.cpp @@ -1,20 +1,140 @@ #include "device_screener.h" -#include -#include -DeviceScreener::DeviceScreener(QObject* parent) : QObject(parent) {} +#include +#include +#include +#include +#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> 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> DeviceScreener::readAdbDevices() { + QProcess process; + process.start("adb", QStringList() << "devices"); + process.waitForFinished(); + + const QString output = process.readAllStandardOutput(); + // qDebug() << "[adb devices output]:\n" << output; + 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) { + 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; -} \ No newline at end of file +} diff --git a/services/device_screener.h b/services/device_screener.h index 7e23a0f..54266f2 100644 --- a/services/device_screener.h +++ b/services/device_screener.h @@ -1,19 +1,29 @@ #pragma once #include +#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: - void start(); +public slots: + void start(); + void stop(); - signals: - void finished(); +signals: + void finished(); private: bool m_running = true; -}; \ No newline at end of file + + static QList > readAdbDevices(); + + static bool takeScreenshot(const QString &deviceId); + + static DeviceInfo readDeviceInfo(const QString &deviceId); +};