#include "LogArchiveSender.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "dao/SettingsDAO.h" #include "net/NetworkService.h" namespace { QString resolveDbPath() { const QSettings settings("config.ini", QSettings::IniFormat); QString dbPath = settings.value("db/dbPath").toString(); if (dbPath.startsWith("~")) dbPath.replace(0, 1, QDir::homePath()); if (!dbPath.isEmpty() && QFileInfo(dbPath).isRelative()) dbPath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(dbPath); return dbPath; } // Запускается в worker-потоке. Возвращает байты zip или пустой массив. QByteArray buildArchive() { const QString logPath = QCoreApplication::applicationDirPath() + "/application.log"; const QString dbPath = resolveDbPath(); const bool hasLog = QFile::exists(logPath); const bool hasDb = !dbPath.isEmpty() && QFile::exists(dbPath); if (!hasLog && !hasDb) { qWarning() << "[LogArchiveSender] neither log nor db exists — nothing to archive"; return {}; } const QString tmpDir = QDir::tempPath() + QStringLiteral("/arc_logs_auto_") + QString::number(QDateTime::currentMSecsSinceEpoch()); QDir().mkpath(tmpDir); if (hasLog) { QFile::copy(logPath, tmpDir + "/application.log"); } if (hasDb) { const QString dbBaseName = QFileInfo(dbPath).fileName(); QFile::copy(dbPath, tmpDir + "/" + dbBaseName); for (const QString &suffix : {QStringLiteral("-wal"), QStringLiteral("-shm")}) { const QString sidecar = dbPath + suffix; if (QFile::exists(sidecar)) QFile::copy(sidecar, tmpDir + "/" + dbBaseName + suffix); } } const QString archivePath = tmpDir + ".zip"; QFile::remove(archivePath); QProcess archiver; archiver.setWorkingDirectory(tmpDir); #ifdef Q_OS_WIN archiver.start("powershell", QStringList() << "-NoProfile" << "-Command" << QString("Compress-Archive -Path '%1/*' -DestinationPath '%2' -Force").arg(tmpDir, archivePath)); #else archiver.start("zip", QStringList() << "-r" << archivePath << "."); #endif archiver.waitForFinished(30000); QDir(tmpDir).removeRecursively(); if (!QFile::exists(archivePath)) { qWarning() << "[LogArchiveSender] archive not created at" << archivePath; return {}; } QFile zip(archivePath); if (!zip.open(QIODevice::ReadOnly)) { qWarning() << "[LogArchiveSender] cannot open archive" << archivePath; QFile::remove(archivePath); return {}; } const QByteArray bytes = zip.readAll(); zip.close(); QFile::remove(archivePath); return bytes; } } // namespace bool LogArchiveSender::dispatch(const QString &label, int eventId, const QString &extraCaptionHtml) { const QString apiToken = SettingsDAO::get("api_key"); const QDateTime now = QDateTime::currentDateTime(); const QString timestamp = now.toString(Qt::ISODate); // api-токен в caption больше не пишем (общий на нескольких операторов); // идентификация идёт по Profile/Material/Phone из extraCaptionHtml. const QString desktopId = SettingsDAO::get("desktop_id"); QStringList head{QStringLiteral("[LOGS]")}; if (!label.isEmpty()) head << QStringLiteral("[%1]").arg(label.toHtmlEscaped()); QString text = QStringLiteral("%1\n%2") .arg(head.join(QStringLiteral(" ")), timestamp.toHtmlEscaped()); if (!desktopId.isEmpty()) text += QStringLiteral("\nDesktop: %1").arg(desktopId.toHtmlEscaped()); if (!extraCaptionHtml.isEmpty()) text += QStringLiteral("\n") + extraCaptionHtml; QString tokenSafe = apiToken; tokenSafe.replace(QRegularExpression(R"([^\w\-.])"), "_"); const QString stamp = now.toString("yyyy-MM-dd_HH-mm-ss"); QStringList parts{QStringLiteral("LOGS")}; if (!tokenSafe.isEmpty()) parts << tokenSafe; parts << stamp; if (!label.isEmpty()) parts << label; if (eventId > 0) parts << QStringLiteral("ev%1").arg(eventId); const QString archiveFilename = parts.join(QStringLiteral("_")) + QStringLiteral(".zip"); auto *worker = QThread::create([text, archiveFilename]() { const QByteArray archive = buildArchive(); if (archive.isEmpty()) { qWarning() << "[LogArchiveSender] empty archive, skipping dispatch"; return; } auto *thread = new QThread; auto *svc = new NetworkService; svc->moveToThread(thread); QObject::connect(thread, &QThread::started, svc, [svc, text, archive, archiveFilename]() { svc->postMonitoringDump(text, archive, QByteArray(), archiveFilename); emit svc->finished(); }); QObject::connect(svc, &NetworkService::finished, thread, &QThread::quit); QObject::connect(svc, &NetworkService::finished, svc, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); }); QObject::connect(worker, &QThread::finished, worker, &QObject::deleteLater); worker->start(); return true; }