#include #include #include "dao/AccountDAO.h" #include "views/MainWindow.h" #include "database/DatabaseManager.h" #include "database/dao/CommonDataDAO.h" #include "database/dao/DeviceDAO.h" #include "services/DeviceScreener.h" #include #include #include #include #include #include #include #include #include #include "AppLogger.h" #include "dao/GeneralLogDAO.h" #include "dao/SettingsDAO.h" #include "EventHandler.h" #include "net/NetworkService.h" #include "black/PayByCardScript.h" #include "ozon/PayByPhoneScript.h" #include "widget/loader/ErrorWindow.h" #include "widget/loader/LoaderWindow.h" #include "widget/loader/RegistrationWindow.h" #include #include #include void startDeviceScreener(const QCoreApplication &app) { auto *thread = new QThread(); auto *deviceScreenerWorker = new DeviceScreener; // Перемещаем worker в новый поток thread. // Это значит, что все слоты worker, вызываемые через connect(), теперь будут исполняться в этом фоне-потоке. deviceScreenerWorker->moveToThread(thread); // Когда поток запустится, автоматически вызовется worker->start() внутри фонового потока. QObject::connect(thread, &QThread::started, deviceScreenerWorker, &DeviceScreener::start); // 🔁 Эти 3 строки делают автоматическую зачистку: // Когда worker закончит работу (emit finished()), мы: // - останавливаем поток (thread->quit()) // - удаляем worker (deleteLater) // - удаляем thread (deleteLater) // Это защищает от утечек памяти и оставшихся потоков после завершения. QObject::connect(deviceScreenerWorker, &DeviceScreener::finished, thread, &QThread::quit); QObject::connect(deviceScreenerWorker, &DeviceScreener::finished, deviceScreenerWorker, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); // Когда пользователь закрывает приложение (Ctrl+C, exit() и т.п.), мы вызываем worker->stop() — ты сам пишешь, что в этом методе. //Обычно он просто ставит флаг m_running = false, чтобы остановить бесконечный цикл в start() QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() { deviceScreenerWorker->stop(); }); thread->start(); } void startEventHandler(const QCoreApplication &app) { auto *thread = new QThread; auto *eventHandler = new EventHandler; eventHandler->moveToThread(thread); QObject::connect(thread, &QThread::started, eventHandler, &EventHandler::start); QObject::connect(eventHandler, &EventHandler::finished, thread, &QThread::quit); QObject::connect(eventHandler, &EventHandler::finished, eventHandler, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() { eventHandler->stop(); }); thread->start(); } void startPayByPhoneTest() { // Берём первый девайс с ozon аккаунтом QList accounts = AccountDAO::getAccountsByAppCode("ozon"); if (accounts.isEmpty()) { qWarning() << "[PayByPhoneTest] No ozon accounts found"; return; } AccountInfo account = accounts.first(); qDebug() << "[PayByPhoneTest] Using account:" << account.id << account.deviceId << account.lastNumbers; auto *thread = new QThread(nullptr); auto *worker = new Ozon::PayByPhoneScript( account, "+79855532896", "Альфа-Банк", 150, nullptr ); worker->moveToThread(thread); QObject::connect(thread, &QThread::started, worker, &Ozon::PayByPhoneScript::start); QObject::connect(worker, &Ozon::PayByPhoneScript::finished, thread, &QThread::quit); QObject::connect(worker, &Ozon::PayByPhoneScript::finished, worker, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater); QObject::connect(worker, &Ozon::PayByPhoneScript::finishedWithResult, [](const QString &result) { qDebug() << "[PayByPhoneTest] Result:" << result; }); thread->start(); } void startBlackPayByCardTest() { QList accounts = AccountDAO::getAccountsByAppCode("black"); if (accounts.isEmpty()) { qWarning() << "[BlackPayByCardTest] No black accounts found"; return; } AccountInfo account = accounts.first(); qDebug() << "[BlackPayByCardTest] Using account:" << account.id << account.deviceId << account.lastNumbers; auto *thread = new QThread(nullptr); auto *worker = new Black::PayByCardScript( account, "0070359630004039066690", 500, nullptr ); worker->moveToThread(thread); QObject::connect(thread, &QThread::started, worker, &Black::PayByCardScript::start); QObject::connect(worker, &Black::PayByCardScript::finished, thread, &QThread::quit); QObject::connect(worker, &Black::PayByCardScript::finished, worker, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater); QObject::connect(worker, &Black::PayByCardScript::finishedWithResult, [](const QString &result) { qDebug() << "[BlackPayByCardTest] Result:" << result; }); thread->start(); } void startNetworkService(const QCoreApplication &app) { auto *paymentThread = new QThread; auto *networkService = new NetworkService; AppLogger::setNetworkService(networkService); networkService->moveToThread(paymentThread); QObject::connect(paymentThread, &QThread::started, networkService, &NetworkService::start); QObject::connect(networkService, &NetworkService::finished, paymentThread, &QThread::quit); QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater); QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater); QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() { networkService->stop(); }); // Сигнал эмитируется из фонового потока → Qt::QueuedConnection доставит его в главный поток QObject::connect(networkService, &NetworkService::tokenRefreshFailed, qApp, []() { QMessageBox *box = new QMessageBox( QMessageBox::Critical, "Ошибка авторизации", "Не удалось обновить токен авторизации.\n\n" "Проверьте подключение к интернету.\n" "Приложение продолжит попытки автоматически.", QMessageBox::Ok ); box->setAttribute(Qt::WA_DeleteOnClose); box->show(); }, Qt::QueuedConnection); paymentThread->start(); } static QFile logFile; static QMutex logMutex; static std::atomic dbReady{false}; void messageHandler( const QtMsgType type, const QMessageLogContext &context, const QString &msg ) { Q_UNUSED(context) const QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"); QString level; switch (type) { case QtDebugMsg: level = "DEBUG"; break; case QtInfoMsg: level = "INFO"; break; case QtWarningMsg: level = "WARNING"; break; case QtCriticalMsg: level = "CRITICAL"; break; case QtFatalMsg: level = "FATAL"; break; } const QString source = context.function ? QString("%1:%2").arg(context.function).arg(context.line) : QString(); const QString line = QString("%1 [%2] [%3:%4] %5\n") .arg(timestamp, level, context.function, QString::number(context.line), msg); // Записываем в файл (потокобезопасно) { QMutexLocker locker(&logMutex); if (logFile.isOpen()) { QTextStream(&logFile) << line; logFile.flush(); } } // Дублируем в БД (с защитой от рекурсии) if (dbReady.load()) { static thread_local bool writing = false; if (!writing) { writing = true; GeneralLogDAO::insert(level, source, msg); writing = false; } } // По желанию — дублировать в консоль const QByteArray localMsg = line.toLocal8Bit(); fprintf(stderr, "%s", localMsg.constData()); if (type == QtFatalMsg) { abort(); } } int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setApplicationName("ARC"); app.setWindowIcon(QIcon("arc.png")); // Выбрать стиль Fusion + принудительная светлая тема QApplication::setStyle(QStyleFactory::create("Fusion")); QPalette lightPalette; lightPalette.setColor(QPalette::Window, QColor(240, 240, 240)); lightPalette.setColor(QPalette::WindowText, Qt::black); lightPalette.setColor(QPalette::Base, Qt::white); lightPalette.setColor(QPalette::AlternateBase, QColor(233, 233, 233)); lightPalette.setColor(QPalette::ToolTipBase, QColor(255, 255, 220)); lightPalette.setColor(QPalette::ToolTipText, Qt::black); lightPalette.setColor(QPalette::Text, Qt::black); lightPalette.setColor(QPalette::Button, QColor(240, 240, 240)); lightPalette.setColor(QPalette::ButtonText, Qt::black); lightPalette.setColor(QPalette::BrightText, Qt::red); lightPalette.setColor(QPalette::Link, QColor(0, 0, 255)); lightPalette.setColor(QPalette::Highlight, QColor(76, 163, 224)); lightPalette.setColor(QPalette::HighlightedText, Qt::white); QApplication::setPalette(lightPalette); logFile.setFileName("application.log"); if (!logFile.open(QIODevice::Append | QIODevice::Text)) { qWarning() << "Не удалось открыть лог-файл для записи"; } // Устанавливаем свой обработчик qInstallMessageHandler(messageHandler); // Проверяем наличие файла БД, если нет — предлагаем загрузить { QSettings settings("config.ini", QSettings::IniFormat); QString dbPath = settings.value("db/dbPath").toString(); if (dbPath.startsWith("~")) dbPath.replace(0, 1, QDir::homePath()); if (!QFile::exists(dbPath)) { QMessageBox msgBox; msgBox.setWindowTitle("База данных не найдена"); msgBox.setText("Файл базы данных не найден:\n" + dbPath); QPushButton *selectBtn = msgBox.addButton("Выбрать файл", QMessageBox::AcceptRole); QPushButton *createBtn = msgBox.addButton("Создать новую", QMessageBox::ActionRole); QPushButton *exitBtn = msgBox.addButton("Выход", QMessageBox::RejectRole); msgBox.setDefaultButton(selectBtn); msgBox.exec(); if (msgBox.clickedButton() == selectBtn) { QString selected = QFileDialog::getOpenFileName( nullptr, "Выберите файл базы данных", QDir::homePath(), "SQLite (*.db *.sqlite);;Все файлы (*)"); if (selected.isEmpty()) { return 0; } QDir().mkpath(QFileInfo(dbPath).absolutePath()); if (!QFile::copy(selected, dbPath)) { QMessageBox::critical(nullptr, "Ошибка", "Не удалось скопировать файл базы данных."); return 1; } } else if (msgBox.clickedButton() == createBtn) { // Просто продолжаем — initSchema создаст новую БД } else { return 0; } } } // Миграции БД DatabaseManager::instance().initSchema(); dbReady.store(true); // 1. Если есть api_key — логинимся и проверяем/создаём desktop // - ошибка сети или сервера [code -1] // - неверный api_key [code 0] — показываем окно ввода // - всё ок [code 1] — запускаем приложение // 2. Если нет api_key — показываем окно для ввода const QString apiKey = SettingsDAO::get("api_key"); auto showRegistration = [&app]() { auto *registrationWindow = new RegistrationWindow(); registrationWindow->setAttribute(Qt::WA_DeleteOnClose); registrationWindow->show(); QObject::connect( registrationWindow, &RegistrationWindow::onRegisteredSuccess, registrationWindow, [registrationWindow, &app]() { auto *mainWindow = new MainWindow(); mainWindow->show(); registrationWindow->close(); startNetworkService(app); startDeviceScreener(app); startEventHandler(app); // startBlackPayByCardTest(); }, Qt::QueuedConnection); }; if (apiKey.isEmpty()) { showRegistration(); } else { auto *loader = new LoaderWindow(); loader->setAttribute(Qt::WA_DeleteOnClose); loader->show(); QObject::connect(loader, &LoaderWindow::loadingFinished, loader, [loader, &app, showRegistration](const int result) { if (result == -1) { auto *errorWindow = new ErrorWindow( "Сервер не отвечает.\n\nПроверьте подключение к интернету и попробуйте снова.", true ); errorWindow->setAttribute(Qt::WA_DeleteOnClose); errorWindow->show(); QObject::connect(errorWindow, &ErrorWindow::retryRequested, errorWindow, [errorWindow, &app, showRegistration]() { errorWindow->close(); auto *newLoader = new LoaderWindow(); newLoader->setAttribute(Qt::WA_DeleteOnClose); newLoader->show(); QObject::connect(newLoader, &LoaderWindow::loadingFinished, newLoader, [newLoader, &app, showRegistration](const int r) { if (r == 1) { auto *mainWindow = new MainWindow(); mainWindow->show(); startNetworkService(app); startDeviceScreener(app); startEventHandler(app); // startBlackPayByCardTest(); } else if (r == 0) { SettingsDAO::remove("api_key"); showRegistration(); } else { auto *err2 = new ErrorWindow( "Сервер не отвечает.\n\nПроверьте подключение к интернету и попробуйте снова.", false ); err2->setAttribute(Qt::WA_DeleteOnClose); err2->show(); } newLoader->close(); }, Qt::QueuedConnection); }); } else if (result == 1) { auto *mainWindow = new MainWindow(); mainWindow->show(); startNetworkService(app); startDeviceScreener(app); startEventHandler(app); // startBlackPayByCardTest(); } else if (result == 0) { SettingsDAO::remove("api_key"); showRegistration(); } loader->close(); }, Qt::QueuedConnection); } return QApplication::exec(); }