Remove database file handling from LogArchiveSender, revise emergency purge logic in monitoring, and enhance error reporting in BankSetupWizard.
This commit is contained in:
parent
521101463b
commit
d7fc838d1a
@ -216,24 +216,78 @@ def _purge_old() -> int:
|
|||||||
return removed
|
return removed
|
||||||
|
|
||||||
|
|
||||||
DISK_EMERGENCY_THRESHOLD = 0.80 # чистить все скрины если диск > 80%
|
DISK_EMERGENCY_THRESHOLD = 0.80 # чистить файлы если диск > 80%
|
||||||
|
|
||||||
|
|
||||||
|
def _disk_pct() -> float:
|
||||||
|
import shutil
|
||||||
|
u = shutil.disk_usage(FILES_DIR)
|
||||||
|
return u.used / u.total
|
||||||
|
|
||||||
|
|
||||||
def _emergency_purge_screenshots() -> int:
|
def _emergency_purge_screenshots() -> int:
|
||||||
"""Удаляет ВСЕ скриншоты истории экрана когда диск переполнен."""
|
"""Постепенно удаляет файлы когда диск > 80%.
|
||||||
import shutil
|
|
||||||
usage = shutil.disk_usage(FILES_DIR)
|
Порядок: сначала скрины (screenshots + screen_dumps) по одному от старых к новым,
|
||||||
if usage.used / usage.total < DISK_EMERGENCY_THRESHOLD:
|
потом zip-дампы (entries.file_path) от старых к новым.
|
||||||
|
"""
|
||||||
|
if _disk_pct() < DISK_EMERGENCY_THRESHOLD:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
removed = 0
|
removed = 0
|
||||||
|
|
||||||
|
# 1. Скрины из таблицы screenshots (самые старые первыми)
|
||||||
with _db() as c:
|
with _db() as c:
|
||||||
rows = c.execute("SELECT image_path FROM screenshots").fetchall()
|
rows = c.execute(
|
||||||
for r in rows:
|
"SELECT rowid, image_path FROM screenshots ORDER BY ts ASC"
|
||||||
_unlink_rel(r["image_path"])
|
).fetchall()
|
||||||
removed = c.execute("DELETE FROM screenshots").rowcount
|
for r in rows:
|
||||||
|
if _disk_pct() < DISK_EMERGENCY_THRESHOLD:
|
||||||
|
break
|
||||||
|
_unlink_rel(r["image_path"])
|
||||||
|
with _db() as c:
|
||||||
|
c.execute("DELETE FROM screenshots WHERE rowid=?", (r["rowid"],))
|
||||||
|
removed += 1
|
||||||
|
|
||||||
|
if _disk_pct() < DISK_EMERGENCY_THRESHOLD:
|
||||||
|
if removed:
|
||||||
|
LOG.warning("disk emergency: removed %d screenshots, disk now %.0f%%", removed, _disk_pct() * 100)
|
||||||
|
return removed
|
||||||
|
|
||||||
|
# 2. Скрины из таблицы screen_dumps (самые старые первыми)
|
||||||
|
with _db() as c:
|
||||||
|
rows = c.execute(
|
||||||
|
"SELECT rowid, image_path, xml_path FROM screen_dumps ORDER BY ts ASC"
|
||||||
|
).fetchall()
|
||||||
|
for r in rows:
|
||||||
|
if _disk_pct() < DISK_EMERGENCY_THRESHOLD:
|
||||||
|
break
|
||||||
|
_unlink_rel(r["image_path"])
|
||||||
|
_unlink_rel(r["xml_path"])
|
||||||
|
with _db() as c:
|
||||||
|
c.execute("DELETE FROM screen_dumps WHERE rowid=?", (r["rowid"],))
|
||||||
|
removed += 1
|
||||||
|
|
||||||
|
if _disk_pct() < DISK_EMERGENCY_THRESHOLD:
|
||||||
|
LOG.warning("disk emergency: removed %d screen files, disk now %.0f%%", removed, _disk_pct() * 100)
|
||||||
|
return removed
|
||||||
|
|
||||||
|
# 3. Zip-дампы из entries (самые старые первыми)
|
||||||
|
with _db() as c:
|
||||||
|
rows = c.execute(
|
||||||
|
"SELECT rowid, file_path FROM entries WHERE file_path IS NOT NULL ORDER BY ts ASC"
|
||||||
|
).fetchall()
|
||||||
|
for r in rows:
|
||||||
|
if _disk_pct() < DISK_EMERGENCY_THRESHOLD:
|
||||||
|
break
|
||||||
|
_unlink_rel(r["file_path"])
|
||||||
|
with _db() as c:
|
||||||
|
c.execute("UPDATE entries SET file_path=NULL WHERE rowid=?", (r["rowid"],))
|
||||||
|
removed += 1
|
||||||
|
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"disk emergency purge: removed %d screenshots (disk %.0f%% full)",
|
"disk emergency purge: removed %d files total, disk now %.0f%%",
|
||||||
removed, usage.used / usage.total * 100,
|
removed, _disk_pct() * 100,
|
||||||
)
|
)
|
||||||
return removed
|
return removed
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,8 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSettings>
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
@ -18,16 +16,6 @@
|
|||||||
|
|
||||||
namespace {
|
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 или пустой массив.
|
// Запускается в worker-потоке. Возвращает байты zip или пустой массив.
|
||||||
QByteArray buildArchive() {
|
QByteArray buildArchive() {
|
||||||
const QString tmpDir = QDir::tempPath() + QStringLiteral("/arc_logs_auto_") +
|
const QString tmpDir = QDir::tempPath() + QStringLiteral("/arc_logs_auto_") +
|
||||||
@ -72,60 +60,46 @@ QByteArray buildArchive() {
|
|||||||
|
|
||||||
bool LogArchiveSender::collectInto(const QString &destDir) {
|
bool LogArchiveSender::collectInto(const QString &destDir) {
|
||||||
const QString logPath = QCoreApplication::applicationDirPath() + "/application.log";
|
const QString logPath = QCoreApplication::applicationDirPath() + "/application.log";
|
||||||
const QString dbPath = resolveDbPath();
|
|
||||||
|
|
||||||
const bool hasLog = QFile::exists(logPath);
|
if (!QFile::exists(logPath)) {
|
||||||
const bool hasDb = !dbPath.isEmpty() && QFile::exists(dbPath);
|
qWarning() << "[LogArchiveSender] log file not found — nothing to collect";
|
||||||
if (!hasLog && !hasDb) {
|
|
||||||
qWarning() << "[LogArchiveSender] neither log nor db exists — nothing to collect";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir().mkpath(destDir);
|
QDir().mkpath(destDir);
|
||||||
bool any = false;
|
bool any = false;
|
||||||
if (hasLog) {
|
QFile srcLog(logPath);
|
||||||
QFile srcLog(logPath);
|
if (srcLog.open(QIODevice::ReadOnly)) {
|
||||||
if (srcLog.open(QIODevice::ReadOnly)) {
|
const QDateTime cutoff = QDateTime::currentDateTime().addSecs(-3600);
|
||||||
const QDateTime cutoff = QDateTime::currentDateTime().addSecs(-3600);
|
|
||||||
|
|
||||||
// Binary search for the first line with ts >= cutoff.
|
// Binary search for the first line with ts >= cutoff.
|
||||||
// Relies on monotonically non-decreasing timestamps in the log.
|
// Relies on monotonically non-decreasing timestamps in the log.
|
||||||
qint64 lo = 0, hi = srcLog.size();
|
qint64 lo = 0, hi = srcLog.size();
|
||||||
while (lo < hi) {
|
while (lo < hi) {
|
||||||
const qint64 mid = lo + (hi - lo) / 2;
|
const qint64 mid = lo + (hi - lo) / 2;
|
||||||
srcLog.seek(mid);
|
srcLog.seek(mid);
|
||||||
if (mid > 0) srcLog.readLine(); // align to next line boundary
|
if (mid > 0) srcLog.readLine(); // align to next line boundary
|
||||||
const qint64 linePos = srcLog.pos();
|
const qint64 linePos = srcLog.pos();
|
||||||
if (linePos >= hi) { hi = mid; continue; }
|
if (linePos >= hi) { hi = mid; continue; }
|
||||||
const QByteArray probe = srcLog.readLine();
|
const QByteArray probe = srcLog.readLine();
|
||||||
if (probe.isEmpty()) { hi = mid; continue; }
|
if (probe.isEmpty()) { hi = mid; continue; }
|
||||||
const QDateTime ts = QDateTime::fromString(
|
const QDateTime ts = QDateTime::fromString(
|
||||||
QString::fromLatin1(probe.left(23)), "yyyy-MM-dd hh:mm:ss.zzz");
|
QString::fromLatin1(probe.left(23)), "yyyy-MM-dd hh:mm:ss.zzz");
|
||||||
if (!ts.isValid() || ts < cutoff)
|
if (!ts.isValid() || ts < cutoff)
|
||||||
lo = srcLog.pos(); // this line is old, search upper half
|
lo = srcLog.pos(); // this line is old, search upper half
|
||||||
else
|
else
|
||||||
hi = linePos; // this line is new, search lower half
|
hi = linePos; // this line is new, search lower half
|
||||||
}
|
}
|
||||||
srcLog.seek(lo);
|
srcLog.seek(lo);
|
||||||
|
|
||||||
QFile dstLog(destDir + "/application.log");
|
QFile dstLog(destDir + "/application.log");
|
||||||
if (dstLog.open(QIODevice::WriteOnly)) {
|
if (dstLog.open(QIODevice::WriteOnly)) {
|
||||||
while (!srcLog.atEnd())
|
while (!srcLog.atEnd())
|
||||||
dstLog.write(srcLog.readLine());
|
dstLog.write(srcLog.readLine());
|
||||||
any = true;
|
any = true;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (QFile::copy(logPath, destDir + "/application.log")) any = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasDb) {
|
|
||||||
const QString dbBaseName = QFileInfo(dbPath).fileName();
|
|
||||||
if (QFile::copy(dbPath, destDir + "/" + dbBaseName)) any = true;
|
|
||||||
for (const QString &suffix : {QStringLiteral("-wal"), QStringLiteral("-shm")}) {
|
|
||||||
const QString sidecar = dbPath + suffix;
|
|
||||||
if (QFile::exists(sidecar))
|
|
||||||
QFile::copy(sidecar, destDir + "/" + dbBaseName + suffix);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (QFile::copy(logPath, destDir + "/application.log")) any = true;
|
||||||
}
|
}
|
||||||
return any;
|
return any;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
// Headless-варианты сборки и отправки в Telegram-relay архива из application.log
|
// Headless-варианты сборки и отправки в Telegram-relay архива из application.log.
|
||||||
// + БД (+ WAL/SHM). Используется EventHandler-ом для авто-отправки при ошибках задач.
|
// Используется EventHandler-ом для авто-отправки при ошибках задач.
|
||||||
// FileLogWindow по-прежнему имеет свою UI-обвязку для ручной отправки.
|
// FileLogWindow по-прежнему имеет свою UI-обвязку для ручной отправки (включает БД).
|
||||||
namespace LogArchiveSender {
|
namespace LogArchiveSender {
|
||||||
// Собирает архив и постит через свежий NetworkService::postMonitoringDump
|
// Собирает архив и постит через свежий NetworkService::postMonitoringDump
|
||||||
// в собственных background-потоках (worker для zip + thread для HTTP).
|
// в собственных background-потоках (worker для zip + thread для HTTP).
|
||||||
@ -17,8 +17,8 @@ namespace LogArchiveSender {
|
|||||||
// не отслеживается (fire-and-forget, как у sendMonitoringLog).
|
// не отслеживается (fire-and-forget, как у sendMonitoringLog).
|
||||||
bool dispatch(const QString &label, int eventId = -1, const QString &extraCaptionHtml = {});
|
bool dispatch(const QString &label, int eventId = -1, const QString &extraCaptionHtml = {});
|
||||||
|
|
||||||
// Копирует application.log + БД (+WAL/SHM) в destDir (создаёт каталог при
|
// Копирует application.log в destDir (создаёт каталог при необходимости).
|
||||||
// необходимости). Возвращает true, если скопирован хотя бы один файл.
|
// Возвращает true, если файл скопирован.
|
||||||
// Используется TaskDumpRecorder, чтобы вложить логи прямо в архив дампа
|
// Используется TaskDumpRecorder, чтобы вложить логи прямо в архив дампа
|
||||||
// задачи и слать всё одним файлом, а не двумя отдельными архивами.
|
// задачи и слать всё одним файлом, а не двумя отдельными архивами.
|
||||||
bool collectInto(const QString &destDir);
|
bool collectInto(const QString &destDir);
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QRadioButton>
|
#include <QRadioButton>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
#include "ozon/LoginAndCheckAccountsScript.h"
|
#include "ozon/LoginAndCheckAccountsScript.h"
|
||||||
#include "ozon/GetProfileInfoScript.h"
|
#include "ozon/GetProfileInfoScript.h"
|
||||||
@ -20,6 +21,7 @@
|
|||||||
#include "dao/MaterialDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
#include "net/MaterialPayload.h"
|
#include "net/MaterialPayload.h"
|
||||||
|
#include "LogArchiveSender.h"
|
||||||
|
|
||||||
BankSetupWizard::BankSetupWizard(QWidget *parent, const QString &deviceId, const BankProfileInfo &app)
|
BankSetupWizard::BankSetupWizard(QWidget *parent, const QString &deviceId, const BankProfileInfo &app)
|
||||||
: QDialog(parent), m_deviceId(deviceId), m_app(app) {
|
: QDialog(parent), m_deviceId(deviceId), m_app(app) {
|
||||||
@ -738,4 +740,18 @@ void BankSetupWizard::onVerificationError(const QString &error) {
|
|||||||
qWarning() << "[BankSetupWizard] Error:" << error;
|
qWarning() << "[BankSetupWizard] Error:" << error;
|
||||||
m_errorText->setText(humanReadableError(error));
|
m_errorText->setText(humanReadableError(error));
|
||||||
m_stack->setCurrentIndex(4);
|
m_stack->setCurrentIndex(4);
|
||||||
|
|
||||||
|
QStringList extra;
|
||||||
|
const QString clientVersion =
|
||||||
|
QSettings("config.ini", QSettings::IniFormat).value("common/version").toString();
|
||||||
|
if (!clientVersion.isEmpty())
|
||||||
|
extra << QStringLiteral("DesktopVersion: %1").arg(clientVersion.toHtmlEscaped());
|
||||||
|
extra << QStringLiteral("Bank: %1").arg(m_app.code.toHtmlEscaped());
|
||||||
|
extra << QStringLiteral("Device: %1").arg(m_deviceId.toHtmlEscaped());
|
||||||
|
if (!m_app.bankProfileId.isEmpty())
|
||||||
|
extra << QStringLiteral("Profile: %1").arg(m_app.bankProfileId.toHtmlEscaped());
|
||||||
|
if (!m_app.phone.isEmpty())
|
||||||
|
extra << QStringLiteral("Phone: %1").arg(m_app.phone.toHtmlEscaped());
|
||||||
|
extra << QStringLiteral("Error: %1").arg(error.toHtmlEscaped());
|
||||||
|
LogArchiveSender::dispatch(QStringLiteral("SETUP_ERROR"), -1, extra.join(QStringLiteral("\n")));
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user