Imported existing sources

This commit is contained in:
2019-04-01 10:43:36 +02:00
parent eb1c3bf259
commit cd9bfb8ffe
56 changed files with 3694 additions and 0 deletions

30
common.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include <QString>
#include <QHash>
#include <QDate>
#include <QtGlobal>
#include <QMetaType>
#include <algorithm>
using Host = QString;
using Process = QString;
using Filename = QString;
struct Logfile {
QString filename;
QString filepath;
qint64 filesize;
bool gzipCompressed;
};
using ScanResult = QHash<Host, QHash<Process, QHash<QDate, Logfile> > >;
Q_DECLARE_METATYPE(ScanResult)
inline bool scanResultEmpty(const ScanResult &result)
{
return result.isEmpty() || std::all_of(result.constBegin(), result.constEnd(), [](const auto &host){
return host.isEmpty() || std::all_of(host.constBegin(), host.constEnd(), [](const auto &process){
return process.isEmpty();
});
});
}

104
dialogs/graphdialog.cpp Normal file
View File

@@ -0,0 +1,104 @@
#include "graphdialog.h"
#include "ui_graphdialog.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QDateTime>
#include <QDebug>
#include <QLineSeries>
#include <QDateTimeAxis>
#include <QValueAxis>
GraphDialog::GraphDialog(QSqlDatabase &database, QWidget *parent) :
QDialog(parent),
m_ui(std::make_unique<Ui::GraphDialog>()),
m_database(database)
{
m_ui->setupUi(this);
QString sql;
if (m_database.driverName() == "QSQLITE")
{
sql = "SELECT "
"`Timestamp`, "
"COUNT(`Timestamp`) "
"FROM "
"`Logs` "
"GROUP BY "
"STRFTIME('%Y-%m-%d %H:00:00.000', `Timestamp`)";
}
else if (m_database.driverName() == "QMYSQL")
{
sql = "SELECT "
"`Timestamp`, "
"COUNT(`Timestamp`) "
"FROM "
"`Logs` "
"GROUP BY "
"DATE_FORMAT(`Timestamp`, '%Y-%m-%d %H:%i:00.000')";
}
else
qFatal("unknown sql driver");
QSqlQuery query(sql, m_database);
if (query.lastError().isValid())
qCritical() << query.lastError().text();
auto chart = new QtCharts::QChart();
chart->legend()->hide();
chart->setTitle(tr("Charts-Test"));
auto series = new QtCharts::QLineSeries();
chart->addSeries(series);
QDateTime minDt, maxDt;
int maxCount{};
while (query.next())
{
const auto timestampStr = query.value(0).toString();
qDebug() << timestampStr;
const auto timestamp = QDateTime::fromString(timestampStr, QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz"));
Q_ASSERT(timestamp.isValid());
if (minDt.isNull() || timestamp < minDt)
minDt = timestamp;
if (maxDt.isNull() || timestamp > maxDt)
maxDt = timestamp;
const auto count = query.value(1).toInt();
if (count > maxCount)
maxCount = count;
qDebug() << timestamp << count;
series->append(timestamp.toMSecsSinceEpoch(), count);
}
qDebug() << minDt << maxDt;
auto axisX = new QtCharts::QDateTimeAxis;
axisX->setRange(minDt, maxDt);
axisX->setTickCount(20);
axisX->setFormat("HH:mm:ss");
axisX->setTitleText("Timestamp");
chart->addAxis(axisX, Qt::AlignBottom);
series->attachAxis(axisX);
auto axisY = new QtCharts::QValueAxis;
axisY->setMax(maxCount);
axisY->setLabelFormat("%i");
axisY->setTitleText("Logs count");
chart->addAxis(axisY, Qt::AlignLeft);
series->attachAxis(axisY);
m_ui->chartView->setRenderHint(QPainter::Antialiasing);
m_ui->chartView->setChart(chart);
}
GraphDialog::~GraphDialog() = default;

23
dialogs/graphdialog.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <QDialog>
#include <memory>
class QSqlDatabase;
namespace Ui { class GraphDialog; }
class GraphDialog : public QDialog
{
Q_OBJECT
public:
GraphDialog(QSqlDatabase &database, QWidget *parent = nullptr);
~GraphDialog() override;
private:
const std::unique_ptr<Ui::GraphDialog> m_ui;
QSqlDatabase &m_database;
};

55
dialogs/graphdialog.ui Normal file
View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GraphDialog</class>
<widget class="QDialog" name="GraphDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QtCharts::QChartView" name="chartView"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QtCharts::QChartView</class>
<extends>QGraphicsView</extends>
<header>qchartview.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>GraphDialog</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>278</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui>

36
dialogs/opendialog.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "opendialog.h"
#include "ui_opendialog.h"
#include <QMessageBox>
#include <QStringBuilder>
#include <QSqlError>
OpenDialog::OpenDialog(QWidget *parent) :
QDialog(parent),
m_ui(std::make_unique<Ui::OpenDialog>())
{
m_ui->setupUi(this);
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &OpenDialog::submit);
}
OpenDialog::~OpenDialog() = default;
QSqlDatabase OpenDialog::database()
{
return m_database;
}
void OpenDialog::submit()
{
m_database = m_ui->databaseWidget->createConnection();
if (!m_database.open())
{
QMessageBox::warning(this, tr("Could not open database!"), tr("Could not open database!") % "\n\n" % m_database.lastError().text());
m_database = {};
return;
}
accept();
}

27
dialogs/opendialog.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <QDialog>
#include <QSqlDatabase>
#include <memory>
namespace Ui { class OpenDialog; }
class OpenDialog : public QDialog
{
Q_OBJECT
public:
explicit OpenDialog(QWidget *parent = nullptr);
~OpenDialog() override;
QSqlDatabase database();
private slots:
void submit();
private:
const std::unique_ptr<Ui::OpenDialog> m_ui;
QSqlDatabase m_database;
};

59
dialogs/opendialog.ui Normal file
View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OpenDialog</class>
<widget class="QDialog" name="OpenDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
<item>
<widget class="DatabaseWidget" name="databaseWidget" native="true"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DatabaseWidget</class>
<extends>QWidget</extends>
<header>widgets/databasewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OpenDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

BIN
failed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

77
gzipdevice.cpp Normal file
View File

@@ -0,0 +1,77 @@
#include "gzipdevice.h"
#include <QFile>
#include <stdexcept>
GzipDevice::GzipDevice(QFile &file, QObject *parent) :
QIODevice(parent),
m_file(file)
{
if (!m_file.isOpen())
throw std::runtime_error("file is not open");
setOpenMode(QIODevice::ReadOnly);
// Prepare inflater status
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
// Initialize inflater
m_result = inflateInit2(&strm, 15 + 16);
if (m_result != Z_OK)
throw std::runtime_error("could not init z_stream");
}
GzipDevice::~GzipDevice()
{
inflateEnd(&strm);
}
bool GzipDevice::isSequential() const
{
return true;
}
bool GzipDevice::atEnd() const
{
return m_result == Z_STREAM_END;
}
qint64 GzipDevice::readData(char *data, qint64 maxlen)
{
if (strm.avail_in == 0)
{
strm.next_in = reinterpret_cast<unsigned char *>(m_readBuffer);
strm.avail_in = m_file.read(m_readBuffer, m_readBufferSize);
}
strm.next_out = reinterpret_cast<unsigned char*>(data);
strm.avail_out = maxlen;
m_result = inflate(&strm, Z_NO_FLUSH);
switch (m_result) {
case Z_NEED_DICT:
throw std::runtime_error("decompression failed: Z_NEED_DICT");
case Z_DATA_ERROR:
throw std::runtime_error("decompression failed: Z_DATA_ERROR");
case Z_MEM_ERROR:
throw std::runtime_error("decompression failed: Z_MEM_ERROR");
case Z_STREAM_ERROR:
throw std::runtime_error("decompression failed: Z_STREAM_ERROR");
}
return maxlen-strm.avail_out;
}
qint64 GzipDevice::writeData(const char *data, qint64 len)
{
Q_UNUSED(data)
Q_UNUSED(len)
qFatal("no writes allowed in GzipDevice!");
return -1;
}

28
gzipdevice.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <QIODevice>
#include "zlib.h"
class QFile;
class GzipDevice : public QIODevice
{
public:
GzipDevice(QFile &file, QObject *parent = nullptr);
~GzipDevice() override;
bool isSequential() const override;
bool atEnd() const override;
protected:
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override;
private:
QFile &m_file;
static constexpr std::size_t m_readBufferSize { 32 * 1024 };
char m_readBuffer[m_readBufferSize];
z_stream strm;
int m_result;
};

BIN
loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

76
loganalyzer.pro Normal file
View File

@@ -0,0 +1,76 @@
QT += core gui widgets network sql charts
TARGET = loganalyzer
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000
CONFIG += c++14
QMAKE_CXXFLAGS_RELEASE -= -O1
QMAKE_CXXFLAGS_RELEASE -= -O2
QMAKE_CXXFLAGS_RELEASE *= -O3
QMAKE_CXXFLAGS_RELEASE -= -march=x86-64
QMAKE_CXXFLAGS_RELEASE -= -mtune=generic
QMAKE_CXXFLAGS_RELEASE *= -march=native
QMAKE_CXXFLAGS_RELEASE *= -mtune=native
LIBS += -lz
SOURCES += main.cpp \
mainwindow.cpp \
wizard/importwizard.cpp \
wizard/intropage.cpp \
wizard/databasepage.cpp \
wizard/importtypepage.cpp \
wizard/localimportpage.cpp \
wizard/conclusionpage.cpp \
wizard/remoteimportoverviewpage.cpp \
wizard/tablespage.cpp \
threads/tablecreatorthread.cpp \
models/checklistmodel.cpp \
threads/remotescannerthread.cpp \
wizard/remoteimportscanpage.cpp \
threads/importthread.cpp \
wizard/importprogresspage.cpp \
dialogs/opendialog.cpp \
widgets/fileselectionwidget.cpp \
widgets/databasewidget.cpp \
gzipdevice.cpp \
dialogs/graphdialog.cpp \
models/sqlrelationaltablemodel.cpp
HEADERS += \
mainwindow.h \
wizard/importwizard.h \
wizard/intropage.h \
wizard/databasepage.h \
wizard/importtypepage.h \
wizard/localimportpage.h \
wizard/conclusionpage.h \
wizard/remoteimportoverviewpage.h \
wizard/tablespage.h \
threads/tablecreatorthread.h \
models/checklistmodel.h \
threads/remotescannerthread.h \
wizard/remoteimportscanpage.h \
common.h \
threads/importthread.h \
wizard/importprogresspage.h \
dialogs/opendialog.h \
widgets/fileselectionwidget.h \
widgets/databasewidget.h \
gzipdevice.h \
dialogs/graphdialog.h \
models/sqlrelationaltablemodel.h
FORMS += \
mainwindow.ui \
dialogs/opendialog.ui \
widgets/fileselectionwidget.ui \
widgets/databasewidget.ui \
wizard/intropage.ui \
wizard/databasepage.ui \
dialogs/graphdialog.ui
RESOURCES += \
resources.qrc

17
main.cpp Normal file
View File

@@ -0,0 +1,17 @@
#include <QApplication>
#include <QMetaType>
#include "mainwindow.h"
#include "common.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qRegisterMetaType<ScanResult>();
MainWindow w;
w.show();
return a.exec();
}

217
mainwindow.cpp Normal file
View File

@@ -0,0 +1,217 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QSqlRelationalDelegate>
#include <QSqlQuery>
#include <QSqlError>
#include <QMessageBox>
#include <QStringBuilder>
#include <QMenu>
#include <QAction>
#include <QDateTime>
#include <QSqlRecord>
#include <QSqlRelation>
#include <QDebug>
#include "wizard/importwizard.h"
#include "dialogs/opendialog.h"
#include "dialogs/graphdialog.h"
#include "models/sqlrelationaltablemodel.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
m_ui(std::make_unique<Ui::MainWindow>())
{
m_ui->setupUi(this);
m_ui->actionNew->setShortcut(QKeySequence::New);
m_ui->actionOpen->setShortcut(QKeySequence::Open);
m_ui->actionQuit->setShortcut(QKeySequence::Quit);
connect(m_ui->actionNew, &QAction::triggered, this, &MainWindow::newClicked);
connect(m_ui->actionOpen, &QAction::triggered, this, &MainWindow::openClicked);
connect(m_ui->actionClose, &QAction::triggered, this, &MainWindow::closeClicked);
connect(m_ui->actionQuit, &QAction::triggered, QCoreApplication::instance(), &QCoreApplication::quit);
connect(m_ui->actionGraph, &QAction::triggered, this, &MainWindow::graphClicked);
for (QAction *action : { m_ui->actionTimestamp, m_ui->actionHost, m_ui->actionProcess, m_ui->actionFilename, m_ui->actionThread, m_ui->actionType, m_ui->actionMessage })
connect(action, &QAction::toggled, this, &MainWindow::showColumns);
connect(m_ui->lineEdit, &QLineEdit::returnPressed, this, &MainWindow::updateQuery);
connect(m_ui->pushButton, &QPushButton::pressed, this, &MainWindow::updateQuery);
m_ui->tableView->setItemDelegate(new QSqlRelationalDelegate(m_ui->tableView));
connect(m_ui->tableView, &QWidget::customContextMenuRequested, this, &MainWindow::showContextMenu);
}
MainWindow::~MainWindow() = default;
void MainWindow::newClicked()
{
ImportWizard wizard(this);
if (wizard.exec() == QDialog::Accepted && wizard.field("open").toBool())
{
m_ui->actionNew->setVisible(false);
m_ui->actionOpen->setVisible(false);
m_ui->actionClose->setVisible(true);
m_ui->actionGraph->setEnabled(true);
m_ui->lineEdit->setEnabled(true);
m_ui->pushButton->setEnabled(true);
m_database = wizard.database();
setupModel();
updateQuery();
}
}
void MainWindow::openClicked()
{
OpenDialog dialog(this);
if (dialog.exec() == QDialog::Accepted)
{
m_ui->actionNew->setVisible(false);
m_ui->actionOpen->setVisible(false);
m_ui->actionClose->setVisible(true);
m_ui->actionGraph->setEnabled(true);
m_ui->lineEdit->setEnabled(true);
m_ui->pushButton->setEnabled(true);
m_database = dialog.database();
setupModel();
updateQuery();
}
}
void MainWindow::closeClicked()
{
m_ui->actionNew->setVisible(true);
m_ui->actionOpen->setVisible(true);
m_ui->actionClose->setVisible(false);
m_ui->actionGraph->setEnabled(false);
m_ui->lineEdit->setEnabled(false);
m_ui->pushButton->setEnabled(false);
m_ui->tableView->setModel(nullptr);
m_model = nullptr;
m_database.close();
m_database = QSqlDatabase();
}
void MainWindow::graphClicked()
{
GraphDialog(m_database, this).exec();
}
void MainWindow::updateQuery()
{
auto filter = m_ui->lineEdit->text();
if (!filter.trimmed().isEmpty())
{
filter.replace("||", "OR");
filter.replace("&&", "AND");
m_model->setFilter(filter);
}
if (!m_model->select())
QMessageBox::warning(this, tr("Query failed!"), tr("Query failed!") % "\n\n" % m_model->query().lastError().text());
}
void MainWindow::showColumns()
{
for (const auto &pair : {
std::make_pair(m_ui->actionTimestamp, ColumnTimestamp),
std::make_pair(m_ui->actionHost, ColumnHost),
std::make_pair(m_ui->actionProcess, ColumnProcess),
std::make_pair(m_ui->actionFilename, ColumnFilename),
std::make_pair(m_ui->actionThread, ColumnThread),
std::make_pair(m_ui->actionType, ColumnType),
std::make_pair(m_ui->actionMessage, ColumnMessage)
})
m_ui->tableView->setColumnHidden(std::get<1>(pair), !std::get<0>(pair)->isChecked());
}
void MainWindow::showContextMenu(const QPoint &pos)
{
const auto index = m_ui->tableView->indexAt(pos);
if (!index.isValid())
return;
QMenu menu(this);
const auto exec = [this,&menu,&pos](){ return menu.exec(m_ui->tableView->viewport()->mapToGlobal(pos)); };
qDebug() << m_model->record(index.row()).value(2);
const auto data = m_model->data(index, Qt::EditRole).toString();
switch (index.column())
{
case ColumnTimestamp:
{
auto minute = menu.addAction(tr("Filter by minute"));
auto second = menu.addAction(tr("Filter by second"));
auto action = exec();
if (action == minute || action == second)
{
const auto format = QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz");
auto dateTime = QDateTime::fromString(data, format);
auto time = dateTime.time();
time.setHMS(time.hour(), time.minute(), action==minute ? 0 : time.second());
dateTime.setTime(time);
m_ui->lineEdit->setText(QString("`Timestamp` BETWEEN \"%0\" AND \"%1\"").arg(dateTime.toString(format), dateTime.addMSecs(action==minute ? 59999 : 999).toString(format)));
updateQuery();
}
break;
}
case ColumnHost:
if (menu.addAction(tr("Filter by host")) == exec())
{
m_ui->lineEdit->setText(QString("`Hosts`.`Name` = \"%0\"").arg(data));
updateQuery();
}
break;
case ColumnProcess:
if (menu.addAction(tr("Filter by process")) == exec())
{
m_ui->lineEdit->setText(QString("`Processes`.`Name` = \"%0\"").arg(data));
updateQuery();
}
break;
case ColumnFilename:
if (menu.addAction(tr("Filter by filename")) == exec())
{
m_ui->lineEdit->setText(QString("`Filenames`.`Name` = \"%0\"").arg(data));
updateQuery();
}
break;
case ColumnThread:
if (menu.addAction(tr("Filter by thread")) == exec())
{
m_ui->lineEdit->setText(QString("`Threads`.`Name` = \"%0\"").arg(data));
updateQuery();
}
break;
case ColumnType:
if (menu.addAction(tr("Filter by type")) == exec())
{
m_ui->lineEdit->setText(QString("`Types`.`Name` = \"%0\"").arg(data));
updateQuery();
}
break;
}
}
void MainWindow::setupModel()
{
m_ui->tableView->setModel(nullptr);
m_model = std::make_unique<SqlRelationalTableModel>(this, m_database);
m_model->setTable("Logs");
m_model->setRelation(ColumnHost, QSqlRelation("Hosts", "ID", "Name"));
m_model->setRelation(ColumnProcess, QSqlRelation("Processes", "ID", "Name"));
m_model->setRelation(ColumnFilename, QSqlRelation("Filenames", "ID", "Name"));
m_model->setRelation(ColumnThread, QSqlRelation("Threads", "ID", "Name"));
m_model->setRelation(ColumnType, QSqlRelation("Types", "ID", "Name"));
m_ui->tableView->setModel(m_model.get());
m_ui->tableView->setColumnHidden(ColumnID, true);
showColumns();
}

38
mainwindow.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <QMainWindow>
#include <QSqlDatabase>
#include <memory>
class QSqlRelationalTableModel;
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
private slots:
void newClicked();
void openClicked();
void closeClicked();
void graphClicked();
void updateQuery();
void showColumns();
void showContextMenu(const QPoint &pos);
private:
void setupModel();
enum { ColumnID, ColumnTimestamp, ColumnHost, ColumnProcess, ColumnFilename, ColumnThread, ColumnType, ColumnMessage };
const std::unique_ptr<Ui::MainWindow> m_ui;
QSqlDatabase m_database;
std::unique_ptr<QSqlRelationalTableModel> m_model;
};

216
mainwindow.ui Normal file
View File

@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>965</width>
<height>578</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="lineEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Filter</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="tableView">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>965</width>
<height>20</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="actionNew"/>
<addaction name="actionOpen"/>
<addaction name="actionClose"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>&amp;View</string>
</property>
<addaction name="actionTimestamp"/>
<addaction name="actionHost"/>
<addaction name="actionProcess"/>
<addaction name="actionFilename"/>
<addaction name="actionThread"/>
<addaction name="actionType"/>
<addaction name="actionMessage"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>&amp;Tools</string>
</property>
<addaction name="actionGraph"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menuView"/>
<addaction name="menuTools"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionNew">
<property name="text">
<string>&amp;New project</string>
</property>
</action>
<action name="actionOpen">
<property name="text">
<string>&amp;Open</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>Quit</string>
</property>
</action>
<action name="actionClose">
<property name="text">
<string>Close Database</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="actionTimestamp">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Timestamp</string>
</property>
</action>
<action name="actionHost">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Host</string>
</property>
</action>
<action name="actionProcess">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Process</string>
</property>
</action>
<action name="actionFilename">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="text">
<string>Filename</string>
</property>
</action>
<action name="actionThread">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Thread</string>
</property>
</action>
<action name="actionType">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Type</string>
</property>
</action>
<action name="actionMessage">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Message</string>
</property>
</action>
<action name="actionGraph">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Graph</string>
</property>
</action>
<action name="actionRawData">
<property name="text">
<string>Raw data</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

141
models/checklistmodel.cpp Normal file
View File

@@ -0,0 +1,141 @@
#include "checklistmodel.h"
ChecklistModel::ChecklistModel(QObject *parent) :
QAbstractListModel(parent)
{
}
ChecklistModel::ChecklistModel(const QStringList &items, QObject *parent) :
QAbstractListModel(parent)
{
for (const auto &item : items)
m_items.append(std::make_pair(item, true));
}
ChecklistModel::ChecklistModel(const QList<std::pair<QString, bool> > &items, QObject *parent) :
QAbstractListModel(parent),
m_items(items)
{
}
QStringList ChecklistModel::items() const
{
QStringList items;
for (const auto &pair : m_items)
items.append(std::get<0>(pair));
return items;
}
void ChecklistModel::setItems(const QStringList &items)
{
emit beginResetModel();
m_items.clear();
for (const auto &item : items)
m_items.append(std::make_pair(item, true));
emit endResetModel();
}
void ChecklistModel::setItems(const QList<std::pair<QString, bool> > &items)
{
emit beginResetModel();
m_items = items;
emit endResetModel();
}
QStringList ChecklistModel::enabledItems() const
{
QStringList items;
for (const auto &pair : m_items)
if (std::get<1>(pair))
items.append(std::get<0>(pair));
return items;
}
QStringList ChecklistModel::disabledItems() const
{
QStringList items;
for (const auto &pair : m_items)
if (!std::get<1>(pair))
items.append(std::get<0>(pair));
return items;
}
int ChecklistModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_items.count();
}
QModelIndex ChecklistModel::sibling(int row, int column, const QModelIndex &idx) const
{
if (!idx.isValid() || column != 0 || row >= m_items.count() || row < 0)
return QModelIndex();
return createIndex(row, 0);
}
QMap<int, QVariant> ChecklistModel::itemData(const QModelIndex &index) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid))
return QMap<int, QVariant>{};
const auto &item = m_items.at(index.row());
return QMap<int, QVariant>{{
std::make_pair<int>(Qt::DisplayRole, std::get<0>(item)),
std::make_pair<int>(Qt::EditRole, std::get<0>(item)),
std::make_pair<int>(Qt::CheckStateRole, std::get<1>(item) ? Qt::Checked : Qt::Unchecked)
}};
}
bool ChecklistModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles)
{
if (roles.isEmpty())
return false;
if (std::any_of(roles.keyBegin(), roles.keyEnd(), [](int role) { return role != Qt::CheckStateRole; }))
return false;
auto roleIter = roles.constFind(Qt::CheckStateRole);
Q_ASSERT(roleIter != roles.constEnd());
return setData(index, roleIter.value(), roleIter.key());
}
QVariant ChecklistModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_items.size())
return QVariant();
const auto &item = m_items.at(index.row());
if (role == Qt::DisplayRole || role == Qt::EditRole)
return std::get<0>(item);
if (role == Qt::CheckStateRole)
return std::get<1>(item) ? Qt::Checked : Qt::Unchecked;
return QVariant();
}
bool ChecklistModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() >= 0 && index.row() < m_items.size() && role == Qt::CheckStateRole)
{
auto &item = m_items[index.row()];
std::get<1>(item) = value.toBool();
emit dataChanged(index, index, { Qt::CheckStateRole });
return true;
}
return false;
}
Qt::ItemFlags ChecklistModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return QAbstractListModel::flags(index);
return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable;
}

33
models/checklistmodel.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <QAbstractListModel>
#include <QList>
#include <utility>
class Q_CORE_EXPORT ChecklistModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit ChecklistModel(QObject *parent = nullptr);
explicit ChecklistModel(const QStringList &items, QObject *parent = nullptr);
explicit ChecklistModel(const QList<std::pair<QString, bool> > &strings, QObject *parent = nullptr);
QStringList items() const;
void setItems(const QStringList &items);
void setItems(const QList<std::pair<QString, bool> > &items);
QStringList enabledItems() const;
QStringList disabledItems() const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex sibling(int row, int column, const QModelIndex &idx) const override;
QMap<int, QVariant> itemData(const QModelIndex &index) const override;
bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
QList<std::pair<QString, bool> > m_items;
};

View File

@@ -0,0 +1,14 @@
#include "sqlrelationaltablemodel.h"
SqlRelationalTableModel::SqlRelationalTableModel(QObject *parent, QSqlDatabase db) :
QSqlRelationalTableModel(parent, db)
{
}
QVariant SqlRelationalTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Vertical)
return {};
return QSqlRelationalTableModel::headerData(section, orientation, role);
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <QSqlRelationalTableModel>
class SqlRelationalTableModel : public QSqlRelationalTableModel
{
Q_OBJECT
public:
explicit SqlRelationalTableModel(QObject *parent = nullptr, QSqlDatabase db = QSqlDatabase());
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
};

7
resources.qrc Normal file
View File

@@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/loganalyzer">
<file>loading.gif</file>
<file>failed.png</file>
<file>succeeded.png</file>
</qresource>
</RCC>

BIN
succeeded.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

353
threads/importthread.cpp Normal file
View File

@@ -0,0 +1,353 @@
#include "importthread.h"
#include <QSqlError>
#include <QFile>
#include <QTextStream>
#include <QVariant>
#include <utility>
#include "gzipdevice.h"
ImportThread::ImportThread(QSqlDatabase &database, const ScanResult &result, const QString &timeFormat, QObject *parent) :
QThread(parent),
m_database(database),
m_result(result),
m_timeFormat(timeFormat),
m_queryInsertLog(database),
m_queryFindHost(database),
m_queryInsertHost(database),
m_queryFindProcess(database),
m_queryInsertProcess(database),
m_queryFindFilename(database),
m_queryInsertFilename(database),
m_queryFindThread(database),
m_queryInsertThread(database),
m_queryFindType(database),
m_queryInsertType(database),
m_totalSize(calculateTotalSize())
{
}
void ImportThread::run()
{
{
int query { 0 };
typedef std::tuple<QString, QSqlQuery&, QString> Row;
for (const auto &tuple : {
Row(tr("insert log"), m_queryInsertLog, "INSERT INTO `Logs` (`Timestamp`, `HostID`, `ProcessID`, `FilenameID`, `ThreadID`, `TypeID`, `Message`) "
"VALUES (:Timestamp, :HostID, :ProcessID, :FilenameID, :ThreadID, :TypeID, :Message);"),
Row(tr("find host"), m_queryFindHost, "SELECT `ID` FROM `Hosts` WHERE `Name` = :Name;"),
Row(tr("insert host"), m_queryInsertHost, "INSERT INTO `Hosts` (`Name`) VALUES (:Name);"),
Row(tr("find host"), m_queryFindProcess, "SELECT `ID` FROM `Processes` WHERE `Name` = :Name;"),
Row(tr("insert host"), m_queryInsertProcess, "INSERT INTO `Processes` (`Name`) VALUES (:Name);"),
Row(tr("find host"), m_queryFindFilename, "SELECT `ID` FROM `Filenames` WHERE `Name` = :Name;"),
Row(tr("insert host"), m_queryInsertFilename, "INSERT INTO `Filenames` (`Name`) VALUES (:Name);"),
Row(tr("find host"), m_queryFindThread, "SELECT `ID` FROM `Threads` WHERE `Name` = :Name;"),
Row(tr("insert host"), m_queryInsertThread, "INSERT INTO `Threads` (`Name`) VALUES (:Name);"),
Row(tr("find host"), m_queryFindType, "SELECT `ID` FROM `Types` WHERE `Name` = :Name;"),
Row(tr("insert host"), m_queryInsertType, "INSERT INTO `Types` (`Name`) VALUES (:Name);")
})
{
if (isInterruptionRequested())
return;
emit statusUpdate(tr("Preparing query to %0...").arg(std::get<0>(tuple)));
if (!std::get<1>(tuple).prepare(std::get<2>(tuple)))
{
emit statusUpdate(tr("Failed."));
emit logMessage(tr("Could not prepare query to %0: %1").arg(std::get<0>(tuple), std::get<1>(tuple).lastError().text()));
return;
}
emit progressUpdate(query++, 11);
}
}
qint64 processedSize { 0 };
for (auto hostsIter = m_result.constBegin(); hostsIter != m_result.constEnd(); hostsIter++)
{
if (isInterruptionRequested())
return;
m_queryInsertLog.bindValue(":HostID", getHostID(hostsIter.key()));
for (auto processesIter = hostsIter.value().constBegin(); processesIter != hostsIter.value().constEnd(); processesIter++)
{
if (isInterruptionRequested())
return;
m_queryInsertLog.bindValue(":ProcessID", getProcessID(processesIter.key()));
for (auto datesIter = processesIter.value().constBegin(); datesIter != processesIter.value().constEnd(); datesIter++)
{
if (isInterruptionRequested())
return;
m_queryInsertLog.bindValue(":FilenameID", getFilenameID(datesIter.value().filename));
emit logMessage(datesIter.value().filename);
QFile file(datesIter.value().filepath);
QFile::OpenMode flags = QIODevice::ReadOnly;
if (!datesIter.value().gzipCompressed)
flags |= QIODevice::Text;
if (!file.open(flags))
{
emit logMessage(tr("Could not open logfile: %0").arg(file.errorString()));
continue;
}
struct {
QDateTime dateTime;
QString thread { "main" };
QString type;
QString message;
} test;
const auto insert = [&test,this,&processedSize,&file](){
m_queryInsertLog.bindValue(":Timestamp", test.dateTime.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz")));
m_queryInsertLog.bindValue(":ThreadID", getThreadID(test.thread));
m_queryInsertLog.bindValue(":TypeID", getTypeID(test.type));
m_queryInsertLog.bindValue(":Message", test.message);
if (!m_queryInsertLog.exec())
emit logMessage(tr("could not execute query to insert log: %0").arg(m_queryInsertLog.lastError().text()));
m_logsInserted++;
const auto now = QDateTime::currentDateTime();
if (m_lastProgressUpdate.isNull() || m_lastProgressUpdate.msecsTo(now) >= 100)
{
emit statusUpdate(tr("%0 logs inserted...").arg(m_logsInserted));
emit progressUpdate(processedSize + file.pos(), m_totalSize);
m_lastProgressUpdate = now;
}
};
int indentionOffset;
if (!m_database.transaction())
{
emit statusUpdate(tr("Aborted."));
emit logMessage(tr("Could not start transaction: %0").arg(m_database.lastError().text()));
return;
}
try
{
const std::unique_ptr<GzipDevice> gzipProxy([&datesIter,&file](){
if (datesIter.value().gzipCompressed)
return std::make_unique<GzipDevice>(file);
return std::unique_ptr<GzipDevice>{};
}());
QTextStream textStream(gzipProxy != nullptr ? static_cast<QIODevice*>(gzipProxy.get()) : &file);
while(!textStream.atEnd())
{
if (isInterruptionRequested())
{
if (!m_database.rollback())
{
emit statusUpdate(tr("Aborted."));
emit logMessage(tr("Could not rollback transaction: %0").arg(m_database.lastError().text()));
}
return;
}
auto line = textStream.readLine();
const auto match = m_lineRegex.match(line);
if (match.hasMatch())
{
if (!test.dateTime.isNull())
insert();
test.message = match.captured(4);
{
const auto threadMatch = m_threadRegex.match(test.message);
if (threadMatch.hasMatch())
test.thread = threadMatch.captured(1);
}
test.dateTime = { datesIter.key(), QTime::fromString(match.captured(2), m_timeFormat) };
test.type = match.captured(3);
test.type = test.type.left(test.type.indexOf(':'));
indentionOffset = match.captured(1).length();
}
else
{
if (!test.dateTime.isNull())
{
if (line.length() >= indentionOffset &&
std::all_of(line.constBegin(), line.constBegin() + indentionOffset, [](const QChar &c){ return c == ' '; }))
line.remove(0, indentionOffset);
test.message.append("\n");
test.message.append(line);
}
}
}
if (!test.dateTime.isNull())
insert();
}
catch (const std::exception &ex)
{
emit logMessage(tr("Aborted: %0").arg(ex.what()));
}
if (!m_database.commit())
{
emit statusUpdate(tr("Aborted."));
emit logMessage(tr("Could not commit transaction: %0").arg(m_database.lastError().text()));
return;
}
processedSize += file.size();
emit statusUpdate(tr("%0 logs inserted...").arg(m_logsInserted));
emit progressUpdate(processedSize, m_totalSize);
}
}
}
}
int ImportThread::getHostID(const QString &host)
{
const auto iter = m_hosts.find(host);
if (iter != m_hosts.constEnd())
return *iter;
m_queryFindHost.bindValue(":Name", host);
if (!m_queryFindHost.exec())
qFatal("could not execute query to find host: %s", qPrintable(m_queryFindHost.lastError().text()));
if (m_queryFindHost.next())
{
const auto id = m_queryFindHost.value(0).toInt();
m_hosts.insert(host, id);
return id;
}
m_queryInsertHost.bindValue(":Name", host);
if (!m_queryInsertHost.exec())
qFatal("could not execute query to insert host: %s", qPrintable(m_queryInsertHost.lastError().text()));
const auto id = m_queryInsertHost.lastInsertId().toInt();
m_hosts.insert(host, id);
return id;
}
int ImportThread::getProcessID(const QString &process)
{
const auto iter = m_processes.find(process);
if (iter != m_processes.constEnd())
return *iter;
m_queryFindProcess.bindValue(":Name", process);
if (!m_queryFindProcess.exec())
qFatal("could not execute query to find process: %s", qPrintable(m_queryFindProcess.lastError().text()));
if (m_queryFindProcess.next())
{
const auto id = m_queryFindProcess.value(0).toInt();
m_processes.insert(process, id);
return id;
}
m_queryInsertProcess.bindValue(":Name", process);
if (!m_queryInsertProcess.exec())
qFatal("could not execute query to insert process: %s", qPrintable(m_queryInsertProcess.lastError().text()));
const auto id = m_queryInsertProcess.lastInsertId().toInt();
m_processes.insert(process, id);
return id;
}
int ImportThread::getFilenameID(const QString &filename)
{
const auto iter = m_filenames.find(filename);
if (iter != m_filenames.constEnd())
return *iter;
m_queryFindFilename.bindValue(":Name", filename);
if (!m_queryFindFilename.exec())
qFatal("could not execute query to find filename: %s", qPrintable(m_queryFindFilename.lastError().text()));
if (m_queryFindFilename.next())
{
const auto id = m_queryFindFilename.value(0).toInt();
m_filenames.insert(filename, id);
return id;
}
m_queryInsertFilename.bindValue(":Name", filename);
if (!m_queryInsertFilename.exec())
qFatal("could not execute query to insert filename: %s", qPrintable(m_queryInsertFilename.lastError().text()));
const auto id = m_queryInsertFilename.lastInsertId().toInt();
m_filenames.insert(filename, id);
return id;
}
int ImportThread::getThreadID(const QString &thread)
{
const auto iter = m_threads.find(thread);
if (iter != m_threads.constEnd())
return *iter;
m_queryFindThread.bindValue(":Name", thread);
if (!m_queryFindThread.exec())
qFatal("could not execute query to find thread: %s", qPrintable(m_queryFindThread.lastError().text()));
if (m_queryFindThread.next())
{
const auto id = m_queryFindThread.value(0).toInt();
m_threads.insert(thread, id);
return id;
}
m_queryInsertThread.bindValue(":Name", thread);
if (!m_queryInsertThread.exec())
qFatal("could not execute query to insert thread: %s", qPrintable(m_queryInsertThread.lastError().text()));
const auto id = m_queryInsertThread.lastInsertId().toInt();
m_threads.insert(thread, id);
return id;
}
int ImportThread::getTypeID(const QString &type)
{
const auto iter = m_types.find(type);
if (iter != m_types.constEnd())
return *iter;
m_queryFindType.bindValue(":Name", type);
if (!m_queryFindType.exec())
qFatal("could not execute query to find type: %s", qPrintable(m_queryFindType.lastError().text()));
if (m_queryFindType.next())
{
const auto id = m_queryFindType.value(0).toInt();
m_types.insert(type, id);
return id;
}
m_queryInsertType.bindValue(":Name", type);
if (!m_queryInsertType.exec())
qFatal("could not execute query to insert type: %s", qPrintable(m_queryInsertType.lastError().text()));
const auto id = m_queryInsertType.lastInsertId().toInt();
m_types.insert(type, id);
return id;
}
qint64 ImportThread::calculateTotalSize() const
{
qint64 totalSize { 0 };
for (auto hostsIter = m_result.constBegin(); hostsIter != m_result.constEnd(); hostsIter++)
for (auto processesIter = hostsIter.value().constBegin(); processesIter != hostsIter.value().constEnd(); processesIter++)
for (auto datesIter = processesIter.value().constBegin(); datesIter != processesIter.value().constEnd(); datesIter++)
totalSize += datesIter.value().filesize;
return totalSize;
}

63
threads/importthread.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <QThread>
#include <QSqlQuery>
#include <QRegularExpression>
#include "common.h"
class QSqlDatabase;
class ImportThread : public QThread
{
Q_OBJECT
public:
ImportThread(QSqlDatabase &database, const ScanResult &result, const QString &timeFormat, QObject *parent = nullptr);
signals:
void statusUpdate(const QString &message);
void progressUpdate(qint64 finished, qint64 total);
void logMessage(const QString &message);
protected:
void run() override;
private:
int getHostID(const QString &host);
int getProcessID(const QString &process);
int getFilenameID(const QString &filename);
int getThreadID(const QString &thread);
int getTypeID(const QString &type);
qint64 calculateTotalSize() const;
QSqlDatabase &m_database;
ScanResult m_result;
QString m_timeFormat;
QHash<QString, int> m_hosts;
QHash<QString, int> m_processes;
QHash<QString, int> m_filenames;
QHash<QString, int> m_threads;
QHash<QString, int> m_types;
QSqlQuery m_queryInsertLog;
QSqlQuery m_queryFindHost;
QSqlQuery m_queryInsertHost;
QSqlQuery m_queryFindProcess;
QSqlQuery m_queryInsertProcess;
QSqlQuery m_queryFindFilename;
QSqlQuery m_queryInsertFilename;
QSqlQuery m_queryFindThread;
QSqlQuery m_queryInsertThread;
QSqlQuery m_queryFindType;
QSqlQuery m_queryInsertType;
const qint64 m_totalSize;
quint64 m_logsInserted { 0 };
QDateTime m_lastProgressUpdate;
const QRegularExpression m_lineRegex { "^(([0-9]+:[0-9]+:[0-9]+(?:\\.[0-9]+)?) (FATAL: |ERROR: |WARNING: |INFO: |DEBUG: |DEBUG1: |DEBUG2: ))(.*)$" };
const QRegularExpression m_threadRegex { "----- Thread context: (.*) -----$" };
};

View File

@@ -0,0 +1,86 @@
#include "remotescannerthread.h"
#include <QDirIterator>
#include <QFileInfo>
#include <QStringBuilder>
RemoteScannerThread::RemoteScannerThread(const QString &dir, QObject *parent) :
QThread(parent),
m_dir(dir)
{
}
void RemoteScannerThread::run()
{
scanForHosts();
}
void RemoteScannerThread::scanForHosts()
{
if (m_files)
qFatal("thread was already run");
QDirIterator iter(m_dir, QDir::Dirs | QDir::NoDotAndDotDot);
while (iter.hasNext())
{
if (isInterruptionRequested())
return;
const QFileInfo fileInfo(iter.next());
const auto host = fileInfo.fileName();
emit logMessage(tr("Scanning host %0...").arg(host));
scanForLogfiles(host, fileInfo.absoluteFilePath());
}
emit progressUpdate(m_files, m_files - m_valid);
emit finished(m_result);
}
void RemoteScannerThread::scanForLogfiles(const QString &hostname, const QString &hostDir)
{
auto &hostEntry = m_result[hostname];
QDirIterator dirIter(hostDir, { "*.log.gz" }, QDir::Files);
while (dirIter.hasNext())
{
if (isInterruptionRequested())
return;
const QFileInfo fileInfo(dirIter.next());
m_files++;
const auto match = m_fileExpression.match(fileInfo.fileName());
if (!match.hasMatch())
continue;
const auto process = match.captured(1);
if (process.endsWith(QStringLiteral(".olog")))
continue;
const auto date = QDate::fromString(match.captured(2), QStringLiteral("yyyyMMdd"));
if (date.isNull())
continue;
m_valid++;
Q_ASSERT(!hostEntry[process].contains(date));
hostEntry[process].insert(date, {
hostname % "/" % fileInfo.fileName(),
fileInfo.absoluteFilePath(),
fileInfo.size(),
true
});
const auto now = QDateTime::currentDateTime();
if (m_lastUpdate.isNull() || m_lastUpdate.msecsTo(now) >= 100)
{
emit progressUpdate(m_files, m_files - m_valid);
m_lastUpdate = now;
}
}
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include <QThread>
#include <QRegularExpression>
#include <QSet>
#include <QString>
#include <QDate>
#include "common.h"
class RemoteScannerThread : public QThread
{
Q_OBJECT
const QRegularExpression m_fileExpression { "^(.+)-([0-9]{8})\\.log?" };
public:
RemoteScannerThread(const QString &dir, QObject *parent = nullptr);
signals:
void progressUpdate(int totalFiles, int skippedFiles);
void logMessage(const QString &message);
void finished(const ScanResult &result);
protected:
void run() override;
private:
void scanForHosts();
void scanForLogfiles(const QString &hostname, const QString &hostDir);
QString m_dir;
ScanResult m_result;
int m_files { 0 };
int m_valid { 0 };
QDateTime m_lastUpdate;
};

View File

@@ -0,0 +1,101 @@
#include "tablecreatorthread.h"
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
const QStringList TableCreatorThread::m_tables { "Hosts", "Processes", "Filenames", "Threads", "Types", "Logs" };
TableCreatorThread::TableCreatorThread(QSqlDatabase &database, QObject *parent) :
QThread(parent), m_database(database)
{
}
const QStringList &TableCreatorThread::tables()
{
return m_tables;
}
void TableCreatorThread::run()
{
int index { 0 };
for (const QString tableName : m_tables)
{
if (isInterruptionRequested())
return;
const auto sql = [&tableName,type=m_database.driverName()]() -> QString {
if (tableName == "Logs")
{
if (type == "QSQLITE")
{
return "CREATE TABLE IF NOT EXISTS `Logs` ("
"`ID` INTEGER PRIMARY KEY, "
"`Timestamp` TEXT NOT NULL, "
"`HostID` INTEGER NOT NULL, "
"`ProcessID` INTEGER NOT NULL, "
"`FilenameID` INTEGER NOT NULL, "
"`ThreadID` INTEGER NOT NULL, "
"`TypeID` INTEGER NOT NULL, "
"`Message` TEXT NOT NULL, "
"FOREIGN KEY (`HostID`) REFERENCES `Hosts`(`ID`), "
"FOREIGN KEY (`ProcessID`) REFERENCES `Processes`(`ID`), "
"FOREIGN KEY (`FilenameID`) REFERENCES `Filenames`(`ID`), "
"FOREIGN KEY (`ThreadID`) REFERENCES `Threads`(`ID`), "
"FOREIGN KEY (`TypeID`) REFERENCES `Types`(`ID`)"
");";
}
else if (type == "QMYSQL")
{
return "CREATE TABLE IF NOT EXISTS `Logs` ("
"`ID` INT UNSIGNED NOT NULL AUTO_INCREMENT, "
"`Timestamp` DATETIME NOT NULL, "
"`HostID` INT UNSIGNED NOT NULL, "
"`ProcessID` INT UNSIGNED NOT NULL, "
"`FilenameID` INT UNSIGNED NOT NULL, "
"`ThreadID` INT UNSIGNED NOT NULL, "
"`TypeID` INT UNSIGNED NOT NULL, "
"`Message` LONGTEXT NOT NULL, "
"PRIMARY KEY(`ID`), "
"INDEX(`Timestamp`), "
"FOREIGN KEY (`HostID`) REFERENCES `Hosts`(`ID`), "
"FOREIGN KEY (`ProcessID`) REFERENCES `Processes`(`ID`), "
"FOREIGN KEY (`FilenameID`) REFERENCES `Filenames`(`ID`), "
"FOREIGN KEY (`ThreadID`) REFERENCES `Threads`(`ID`), "
"FOREIGN KEY (`TypeID`) REFERENCES `Types`(`ID`)"
");";
}
}
else
{
if (type == "QSQLITE")
{
return QString("CREATE TABLE IF NOT EXISTS `%0` ("
"`ID` INTEGER PRIMARY KEY, "
"`Name` TEXT NOT NULL UNIQUE"
");").arg(tableName);
}
else if (type == "QMYSQL")
{
return QString("CREATE TABLE IF NOT EXISTS `%0` ("
"`ID` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, "
"`Name` VARCHAR(255) NOT NULL UNIQUE"
");").arg(tableName);
}
}
qFatal("unknown database type %s", qPrintable(type));
}();
QSqlQuery query(sql, m_database);
if (query.lastError().isValid())
{
qCritical() << query.lastError().text();
return;
}
emit someSignal(index++);
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include <QThread>
class QSqlDatabase;
class TableCreatorThread : public QThread
{
Q_OBJECT
static const QStringList m_tables;
public:
explicit TableCreatorThread(QSqlDatabase &database, QObject *parent = nullptr);
static const QStringList &tables();
signals:
void someSignal(int index);
protected:
void run() override;
private:
QSqlDatabase &m_database;
};

104
widgets/databasewidget.cpp Normal file
View File

@@ -0,0 +1,104 @@
#include "databasewidget.h"
#include "ui_databasewidget.h"
DatabaseWidget::DatabaseWidget(QWidget *parent) :
QWidget(parent),
m_ui(std::make_unique<Ui::DatabaseWidget>())
{
m_ui->setupUi(this);
m_ui->comboBox->addItem(tr("SQLite"), "QSQLITE");
m_ui->comboBox->addItem(tr("MySQL"), "QMYSQL");
// for debugging
setDriver("QMYSQL");
// setMysqlHostname("sql7.freemysqlhosting.net");
// setMysqlUsername("sql7285815");
// setMysqlPassword("BKhysrtqKl");
// setMysqlDatabase("sql7285815");
//setMysqlHostname("brunner.ninja");
setMysqlHostname("localhost");
setMysqlUsername("logtest");
setMysqlPassword("logtest");
setMysqlDatabase("logtest");
}
DatabaseWidget::~DatabaseWidget() = default;
QString DatabaseWidget::driver() const
{
return m_ui->comboBox->currentData().toString();
}
void DatabaseWidget::setDriver(const QString &driver)
{
m_ui->comboBox->setCurrentIndex(m_ui->comboBox->findData(driver));
}
QString DatabaseWidget::sqliteFilepath() const
{
return m_ui->fileSelectionWidget->path();
}
void DatabaseWidget::setSqliteFilepath(const QString &sqliteFilepath)
{
m_ui->fileSelectionWidget->setPath(sqliteFilepath);
}
QString DatabaseWidget::mysqlHostname() const
{
return m_ui->lineEditHostname->text();
}
void DatabaseWidget::setMysqlHostname(const QString &mysqlHostname)
{
m_ui->lineEditHostname->setText(mysqlHostname);
}
QString DatabaseWidget::mysqlUsername() const
{
return m_ui->lineEditUsername->text();
}
void DatabaseWidget::setMysqlUsername(const QString &mysqlUsername)
{
m_ui->lineEditUsername->setText(mysqlUsername);
}
QString DatabaseWidget::mysqlPassword() const
{
return m_ui->lineEditPassword->text();
}
void DatabaseWidget::setMysqlPassword(const QString &mysqlPassword)
{
m_ui->lineEditPassword->setText(mysqlPassword);
}
QString DatabaseWidget::mysqlDatabase() const
{
return m_ui->lineEditDatabase->text();
}
void DatabaseWidget::setMysqlDatabase(const QString &mysqlDatabase)
{
m_ui->lineEditDatabase->setText(mysqlDatabase);
}
QSqlDatabase DatabaseWidget::createConnection(const QString &connectionName)
{
auto db = QSqlDatabase::addDatabase(driver(), connectionName);
if (db.driverName() == "QSQLITE")
db.setDatabaseName(sqliteFilepath());
else
{
db.setHostName(mysqlHostname());
db.setUserName(mysqlUsername());
db.setPassword(mysqlPassword());
db.setDatabaseName(mysqlDatabase());
}
return db;
}

40
widgets/databasewidget.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <QWidget>
#include <QSqlDatabase>
#include <memory>
namespace Ui { class DatabaseWidget; }
class DatabaseWidget : public QWidget
{
Q_OBJECT
public:
explicit DatabaseWidget(QWidget *parent = nullptr);
~DatabaseWidget() override;
QString driver() const;
void setDriver(const QString &driver);
QString sqliteFilepath() const;
void setSqliteFilepath(const QString &sqliteFilepath);
QString mysqlHostname() const;
void setMysqlHostname(const QString &mysqlHostname);
QString mysqlUsername() const;
void setMysqlUsername(const QString &mysqlUsername);
QString mysqlPassword() const;
void setMysqlPassword(const QString &mysqlPassword);
QString mysqlDatabase() const;
void setMysqlDatabase(const QString &mysqlDatabase);
QSqlDatabase createConnection(const QString& connectionName = QLatin1String(QSqlDatabase::defaultConnection));
private:
const std::unique_ptr<Ui::DatabaseWidget> m_ui;
};

124
widgets/databasewidget.ui Normal file
View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DatabaseWidget</class>
<widget class="QWidget" name="DatabaseWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>175</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="FileSelectionWidget" name="fileSelectionWidget" native="true"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QFormLayout" name="formLayout">
<item row="1" column="1">
<widget class="QLineEdit" name="lineEditUsername"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEditHostname"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelHostname">
<property name="text">
<string>&lt;b&gt;Hostname:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelUsername">
<property name="text">
<string>&lt;b&gt;Username:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelPassword">
<property name="text">
<string>&lt;b&gt;Password:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelDatabase">
<property name="text">
<string>&lt;b&gt;Database:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEditPassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="lineEditDatabase"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileSelectionWidget</class>
<extends>QWidget</extends>
<header>widgets/fileselectionwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>comboBox</sender>
<signal>currentIndexChanged(int)</signal>
<receiver>stackedWidget</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>101</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,75 @@
#include "fileselectionwidget.h"
#include "ui_fileselectionwidget.h"
#include <QFileDialog>
FileSelectionWidget::FileSelectionWidget(QWidget *parent) :
QWidget(parent),
m_ui(std::make_unique<Ui::FileSelectionWidget>()),
m_mode(Mode::OpenFile)
{
m_ui->setupUi(this);
connect(m_ui->lineEdit, &QLineEdit::textChanged, this, &FileSelectionWidget::pathChanged);
connect(m_ui->pushButton, &QAbstractButton::pressed, this, &FileSelectionWidget::selectPath);
}
FileSelectionWidget::FileSelectionWidget(const Mode mode, QWidget *parent) :
QWidget(parent),
m_ui(std::make_unique<Ui::FileSelectionWidget>()),
m_mode(mode)
{
m_ui->setupUi(this);
connect(m_ui->lineEdit, &QLineEdit::textChanged, this, &FileSelectionWidget::pathChanged);
connect(m_ui->pushButton, &QAbstractButton::pressed, this, &FileSelectionWidget::selectPath);
}
FileSelectionWidget::FileSelectionWidget(const Mode mode, const QString &path, QWidget *parent) :
QWidget(parent),
m_ui(std::make_unique<Ui::FileSelectionWidget>()),
m_mode(mode)
{
m_ui->setupUi(this);
m_ui->lineEdit->setText(path);
connect(m_ui->lineEdit, &QLineEdit::textChanged, this, &FileSelectionWidget::pathChanged);
connect(m_ui->pushButton, &QAbstractButton::pressed, this, &FileSelectionWidget::selectPath);
}
FileSelectionWidget::~FileSelectionWidget() = default;
FileSelectionWidget::Mode FileSelectionWidget::mode() const
{
return m_mode;
}
void FileSelectionWidget::setMode(const FileSelectionWidget::Mode mode)
{
m_mode = mode;
}
QString FileSelectionWidget::path() const
{
return m_ui->lineEdit->text();
}
void FileSelectionWidget::setPath(const QString &path)
{
m_ui->lineEdit->setText(path);
}
void FileSelectionWidget::selectPath()
{
QString path;
switch (m_mode)
{
case Mode::OpenFile: path = QFileDialog::getOpenFileName(this); break;
case Mode::SaveFile: path = QFileDialog::getSaveFileName(this); break;
case Mode::ExistingDirectory: path = QFileDialog::getExistingDirectory(this); break;
}
if (!path.isEmpty())
m_ui->lineEdit->setText(path);
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <QWidget>
#include <memory>
namespace Ui { class FileSelectionWidget; }
class FileSelectionWidget : public QWidget
{
Q_OBJECT
public:
enum class Mode {
OpenFile, SaveFile, ExistingDirectory
};
explicit FileSelectionWidget(QWidget *parent = nullptr);
FileSelectionWidget(const Mode mode, QWidget *parent = nullptr);
FileSelectionWidget(const Mode mode, const QString &path, QWidget *parent = nullptr);
~FileSelectionWidget() override;
Mode mode() const;
void setMode(const Mode mode);
QString path() const;
void setPath(const QString &path);
signals:
void pathChanged(const QString &path);
private slots:
void selectPath();
private:
const std::unique_ptr<Ui::FileSelectionWidget> m_ui;
Mode m_mode;
};

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileSelectionWidget</class>
<widget class="QWidget" name="FileSelectionWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>41</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Select...</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

54
wizard/conclusionpage.cpp Normal file
View File

@@ -0,0 +1,54 @@
#include "conclusionpage.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QCheckBox>
#include <QSqlQuery>
#include <QSqlError>
#include <QMessageBox>
#include <QStringBuilder>
#include <QVariant>
#include "importwizard.h"
ConclusionPage::ConclusionPage(QWidget *parent) :
QWizardPage(parent)
{
setTitle(tr("Conclusion"));
setSubTitle(tr("Import successfully finished!"));
auto layout = new QVBoxLayout;
m_label = new QLabel;
layout->addWidget(m_label);
layout->addStretch(1);
m_checkBox = new QCheckBox(tr("Open new database"));
m_checkBox->setChecked(true);
layout->addWidget(m_checkBox);
registerField("open", m_checkBox);
layout->addStretch(1);
setLayout(layout);
}
void ConclusionPage::initializePage()
{
auto importWizard = qobject_cast<ImportWizard*>(wizard());
Q_ASSERT(importWizard);
Q_ASSERT(importWizard->database().isOpen());
QSqlQuery query("SELECT COUNT(*) FROM `Logs`;", importWizard->database());
if (query.lastError().isValid())
{
QMessageBox::warning(nullptr, tr("Could not get count!"), tr("Could not get count!") % "\n\n" % query.lastError().text());
return;
}
const auto fetched = query.next();
Q_ASSERT(fetched);
m_label->setText(tr("<b>%0 rows</b> have been imported.").arg(query.value(0).toInt()));
}

20
wizard/conclusionpage.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <QWizardPage>
class QLabel;
class QCheckBox;
class ConclusionPage : public QWizardPage
{
Q_OBJECT
public:
explicit ConclusionPage(QWidget *parent = nullptr);
void initializePage() override;
private:
QLabel *m_label;
QCheckBox *m_checkBox;
};

46
wizard/databasepage.cpp Normal file
View File

@@ -0,0 +1,46 @@
#include "databasepage.h"
#include "ui_databasepage.h"
#include <QVBoxLayout>
#include <QSqlError>
#include <QMessageBox>
#include <QStringBuilder>
#include <QSqlQuery>
#include <QFileDialog>
#include "widgets/databasewidget.h"
#include "importwizard.h"
DatabasePage::DatabasePage(QWidget *parent) :
QWizardPage(parent),
m_ui(std::make_unique<Ui::DatabasePage>())
{
m_ui->setupUi(this);
setCommitPage(true);
}
DatabasePage::~DatabasePage() = default;
int DatabasePage::nextId() const
{
return int(ImportWizard::Pages::Tables);
}
bool DatabasePage::validatePage()
{
auto importWizard = qobject_cast<ImportWizard*>(wizard());
Q_ASSERT(importWizard);
Q_ASSERT(!importWizard->database().isOpen());
importWizard->database() = m_ui->databaseWidget->createConnection();
if (!importWizard->database().open())
{
QMessageBox::warning(this, tr("Could not open database!"), tr("Could not open database!") % "\n\n" % importWizard->database().lastError().text());
importWizard->database() = {};
return false;
}
return true;
}

23
wizard/databasepage.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <QWizardPage>
#include <memory>
namespace Ui { class DatabasePage; }
class DatabasePage : public QWizardPage
{
Q_OBJECT
public:
explicit DatabasePage(QWidget *parent = nullptr);
~DatabasePage() override;
int nextId() const override;
bool validatePage() override;
private:
const std::unique_ptr<Ui::DatabasePage> m_ui;
};

51
wizard/databasepage.ui Normal file
View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DatabasePage</class>
<widget class="QWizardPage" name="DatabasePage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>WizardPage</string>
</property>
<property name="title">
<string>Database</string>
</property>
<property name="subTitle">
<string>Please setup the database connection.</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
<item>
<widget class="DatabaseWidget" name="databaseWidget" native="true"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>263</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DatabaseWidget</class>
<extends>QWidget</extends>
<header>widgets/databasewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,117 @@
#include "importprogresspage.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QProgressBar>
#include <QPlainTextEdit>
#include "importwizard.h"
#include "threads/importthread.h"
ImportProgressPage::ImportProgressPage(QWidget *parent) :
QWizardPage(parent)
{
setTitle(tr("Import Progress"));
setSubTitle(tr("TODO..."));
auto layout = new QVBoxLayout;
{
auto hboxLayout = new QHBoxLayout;
m_labelIcon = new QLabel;
hboxLayout->addWidget(m_labelIcon);
m_labelStatus = new QLabel;
hboxLayout->addWidget(m_labelStatus, 1);
layout->addLayout(hboxLayout);
}
m_progressBar = new QProgressBar;
m_progressBar->setMaximum(100);
layout->addWidget(m_progressBar);
m_logView = new QPlainTextEdit;
m_logView->setReadOnly(true);
layout->addWidget(m_logView, 1);
setLayout(layout);
}
ImportProgressPage::~ImportProgressPage() = default;
void ImportProgressPage::initializePage()
{
auto importWizard = qobject_cast<ImportWizard*>(wizard());
Q_ASSERT(importWizard);
Q_ASSERT(importWizard->database().isOpen());
const auto result = wizard()->property("result").value<ScanResult>();
const auto timeFormat = wizard()->property("timeFormat").toString();
m_logView->clear();
m_thread = std::make_unique<ImportThread>(importWizard->database(), result, timeFormat, this);
connect(m_thread.get(), &ImportThread::statusUpdate, this, &ImportProgressPage::statusUpdate);
connect(m_thread.get(), &ImportThread::progressUpdate, this, &ImportProgressPage::progressUpdate);
connect(m_thread.get(), &ImportThread::logMessage, this, &ImportProgressPage::logMessage);
connect(m_thread.get(), &QThread::finished, this, &ImportProgressPage::finished);
m_thread->start();
m_labelIcon->setMovie(&m_movieLoading);
m_movieLoading.start();
}
void ImportProgressPage::cleanupPage()
{
if (m_thread)
{
m_thread->requestInterruption();
m_thread->wait();
m_thread = nullptr;
}
m_movieLoading.stop();
}
int ImportProgressPage::nextId() const
{
return int(ImportWizard::Pages::Conclusion);
}
bool ImportProgressPage::isComplete() const
{
return m_thread == nullptr;
}
void ImportProgressPage::statusUpdate(const QString &message)
{
m_labelStatus->setText(message);
}
void ImportProgressPage::progressUpdate(qint64 finished, qint64 total)
{
while (total & 0xFFFFFFFF00000000)
{
finished = finished >> 8;
total = total >> 8;
}
m_progressBar->setMaximum(total);
m_progressBar->setValue(finished);
}
void ImportProgressPage::logMessage(const QString &message)
{
m_logView->appendHtml(QString("%0: %1<br/>").arg(QTime::currentTime().toString(), message));
}
void ImportProgressPage::finished()
{
cleanupPage();
emit completeChanged();
m_labelIcon->setPixmap(m_pixmapSucceeded);
logMessage(tr("Finished."));
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include <QWizardPage>
#include <QPixmap>
#include <QMovie>
#include <memory>
class QLabel;
class QProgressBar;
class QPlainTextEdit;
class ImportThread;
class ImportProgressPage : public QWizardPage
{
Q_OBJECT
const QPixmap m_pixmapSucceeded { ":/loganalyzer/succeeded.png" };
const QPixmap m_pixmapFailed { ":/loganalyzer/failed.png" };
QMovie m_movieLoading { ":/loganalyzer/loading.gif" };
public:
explicit ImportProgressPage(QWidget *parent = nullptr);
~ImportProgressPage() override;
void initializePage() override;
void cleanupPage() override;
int nextId() const override;
bool isComplete() const override;
private slots:
void statusUpdate(const QString &message);
void progressUpdate(qint64 finished, qint64 total);
void logMessage(const QString &message);
void finished();
private:
QLabel *m_labelIcon;
QLabel *m_labelStatus;
QProgressBar *m_progressBar;
QPlainTextEdit *m_logView;
std::unique_ptr<ImportThread> m_thread;
};

117
wizard/importtypepage.cpp Normal file
View File

@@ -0,0 +1,117 @@
#include "importtypepage.h"
#include <QVBoxLayout>
#include <QRadioButton>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QToolButton>
#include <QDir>
#include <QMessageBox>
#include <QSet>
#include <QFileDialog>
#include "importwizard.h"
#include "common.h"
ImportTypePage::ImportTypePage(QWidget *parent) :
QWizardPage(parent)
{
setTitle(tr("Import type"));
setSubTitle(tr("Please select which type of log files you would like to import."));
auto layout = new QVBoxLayout;
m_radioLocal = new QRadioButton(tr("Local: Typically found under /tmp/testfw_log/tests"));
m_radioLocal->setChecked(true);
layout->addWidget(m_radioLocal);
m_radioRemote = new QRadioButton(tr("Remote: Typically found under /log or /log2"));
layout->addWidget(m_radioRemote);
layout->addStretch(1);
{
auto hboxLayout = new QHBoxLayout;
m_lineEdit = new QLineEdit;
hboxLayout->addWidget(m_lineEdit, 1);
registerField("folder", m_lineEdit);
{
auto toolButton = new QToolButton;
toolButton->setText(tr("Select..."));
connect(toolButton, &QAbstractButton::pressed, this, &ImportTypePage::selectFolder);
hboxLayout->addWidget(toolButton);
}
layout->addLayout(hboxLayout);
}
layout->addStretch(1);
setLayout(layout);
}
int ImportTypePage::nextId() const
{
if (m_radioLocal->isChecked())
return int(ImportWizard::Pages::LocalImport);
if (m_radioRemote->isChecked())
return int(ImportWizard::Pages::RemoteImportScan);
Q_UNREACHABLE();
}
bool ImportTypePage::validatePage()
{
if (m_lineEdit->text().isEmpty())
{
QMessageBox::warning(this, tr("No logfolder defined!"), tr("No logfolder defined!"));
return false;
}
QDir dir(m_lineEdit->text());
if (!dir.exists())
{
QMessageBox::warning(this, tr("Could not find logfolder!"), tr("Could not find logfolder!"));
return false;
}
if (m_radioLocal->isChecked())
{
ScanResult result;
auto &host = result["__dummyHost"];
for (const auto &fileInfo : dir.entryInfoList({ "*.log" }, QDir::Files))
{
if (fileInfo.baseName().endsWith("_console"))
continue;
host[fileInfo.baseName()][QDate()] = {
fileInfo.fileName(),
fileInfo.absoluteFilePath(),
fileInfo.size(),
false
};
}
if (host.isEmpty())
{
QMessageBox::warning(this, tr("Could not find any logs!"), tr("Could not find any logs!"));
return false;
}
wizard()->setProperty("result", QVariant::fromValue(result));
}
if (m_radioRemote->isChecked())
wizard()->setProperty("folder", dir.absolutePath());
return true;
}
void ImportTypePage::selectFolder()
{
const auto path = QFileDialog::getExistingDirectory(this, tr("Select log folder"));
if (!path.isEmpty())
m_lineEdit->setText(path);
}

27
wizard/importtypepage.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <QWizardPage>
class QRadioButton;
class QLineEdit;
class ImportTypePage : public QWizardPage
{
Q_OBJECT
public:
explicit ImportTypePage(QWidget *parent = nullptr);
int nextId() const override;
bool validatePage() override;
private slots:
void selectFolder();
private:
QRadioButton *m_radioLocal;
QRadioButton *m_radioRemote;
QLineEdit *m_lineEdit;
};

35
wizard/importwizard.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "importwizard.h"
#include "intropage.h"
#include "databasepage.h"
#include "tablespage.h"
#include "importtypepage.h"
#include "localimportpage.h"
#include "remoteimportscanpage.h"
#include "remoteimportoverviewpage.h"
#include "importprogresspage.h"
#include "conclusionpage.h"
ImportWizard::ImportWizard(QWidget *parent, Qt::WindowFlags flags) :
QWizard(parent, flags)
{
setPage(int(Pages::Introduction), new IntroPage);
setPage(int(Pages::Database), new DatabasePage);
setPage(int(Pages::Tables), new TablesPage);
setPage(int(Pages::ImportType), new ImportTypePage);
setPage(int(Pages::LocalImport), new LocalImportPage);
setPage(int(Pages::RemoteImportScan), new RemoteImportScanPage);
setPage(int(Pages::RemoteImportOverview), new RemoteImportOverviewPage);
setPage(int(Pages::ImportProgress), new ImportProgressPage);
setPage(int(Pages::Conclusion), new ConclusionPage);
}
QSqlDatabase &ImportWizard::database()
{
return m_database;
}
const QSqlDatabase &ImportWizard::database() const
{
return m_database;
}

20
wizard/importwizard.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <QWizard>
#include <QSqlDatabase>
class ImportWizard : public QWizard
{
Q_OBJECT
public:
enum class Pages { Introduction, Database, Tables, ImportType, LocalImport, RemoteImportScan, RemoteImportOverview, ImportProgress, Conclusion };
ImportWizard(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
QSqlDatabase &database();
const QSqlDatabase &database() const;
private:
QSqlDatabase m_database;
};

18
wizard/intropage.cpp Normal file
View File

@@ -0,0 +1,18 @@
#include "intropage.h"
#include "ui_intropage.h"
#include "importwizard.h"
IntroPage::IntroPage(QWidget *parent) :
QWizardPage(parent),
m_ui(std::make_unique<Ui::IntroPage>())
{
m_ui->setupUi(this);
}
IntroPage::~IntroPage() = default;
int IntroPage::nextId() const
{
return int(ImportWizard::Pages::Database);
}

21
wizard/intropage.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <QWizardPage>
#include <memory>
namespace Ui { class IntroPage; }
class IntroPage : public QWizardPage
{
Q_OBJECT
public:
explicit IntroPage(QWidget *parent = nullptr);
~IntroPage() override;
int nextId() const override;
private:
const std::unique_ptr<Ui::IntroPage> m_ui;
};

34
wizard/intropage.ui Normal file
View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>IntroPage</class>
<widget class="QWizardPage" name="IntroPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>WizardPage</string>
</property>
<property name="title">
<string>Introduction</string>
</property>
<property name="subTitle">
<string>TODO...</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TODO: long introduction...</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

161
wizard/localimportpage.cpp Normal file
View File

@@ -0,0 +1,161 @@
#include "localimportpage.h"
#include <QVBoxLayout>
#include <QFormLayout>
#include <QLineEdit>
#include <QHostInfo>
#include <QDateEdit>
#include <QComboBox>
#include <QListView>
#include <QLabel>
#include <QMessageBox>
#include <QStringBuilder>
#include "importwizard.h"
LocalImportPage::LocalImportPage(QWidget *parent) :
QWizardPage(parent)
{
setTitle(tr("Local Import"));
setSubTitle(tr("TODO..."));
setCommitPage(true);
connect(&m_model, &ChecklistModel::dataChanged, this, &LocalImportPage::updateSummary);
auto layout = new QVBoxLayout;
{
auto hboxLayout = new QHBoxLayout;
{
auto formLayout = new QFormLayout;
m_lineEditHost = new QLineEdit(QHostInfo::localHostName());
formLayout->addRow(tr("Host:"), m_lineEditHost);
m_dateEdit = new QDateEdit(QDate::currentDate());
formLayout->addRow(tr("Date:"), m_dateEdit);
m_comboBox = new QComboBox;
m_comboBox->addItem(tr("Without milliseconds"), "HH:mm:ss");
m_comboBox->addItem(tr("With milliseconds"), "HH:mm:ss.zzz");
formLayout->addRow(tr("Timestamp:"), m_comboBox);
hboxLayout->addLayout(formLayout);
}
{
auto view = new QListView;
view->setModel(&m_model);
hboxLayout->addWidget(view, 1);
}
layout->addLayout(hboxLayout, 1);
}
m_labelSummary = new QLabel;
layout->addWidget(m_labelSummary);
setLayout(layout);
}
int LocalImportPage::nextId() const
{
return int(ImportWizard::Pages::ImportProgress);
}
void LocalImportPage::initializePage()
{
m_result = wizard()->property("result").value<ScanResult>();
Q_ASSERT(m_result.count() == 1);
{
auto processes = m_result.values().first().keys();
processes.sort();
m_model.setItems(processes);
}
updateSummary();
}
bool LocalImportPage::validatePage()
{
auto result = filterResult(m_result);
if (scanResultEmpty(result))
{
QMessageBox::warning(this, tr("No files to import!"), tr("No files to import!"));
return false;
}
Q_ASSERT(result.count() == 1);
auto host = result.values().first();
for (auto iter = host.begin(); iter != host.end(); iter++)
{
auto &dates = iter.value();
Q_ASSERT(dates.count() == 1);
const auto logfile = dates.values().first();
dates.clear();
dates.insert(m_dateEdit->date(), logfile);
}
result.clear();
result.insert(m_lineEditHost->text(), host);
wizard()->setProperty("result", QVariant::fromValue(result));
wizard()->setProperty("timeFormat", m_comboBox->currentData().toString());
return true;
}
void LocalImportPage::updateSummary()
{
if (m_result.isEmpty())
return;
const auto result = filterResult(m_result);
int logFiles { 0 };
qint64 totalSize { 0 };
for (auto hostsIter = result.constBegin(); hostsIter != result.constEnd(); hostsIter++)
for (auto processesIter = hostsIter.value().constBegin(); processesIter != hostsIter.value().constEnd(); processesIter++)
for (auto datesIter = processesIter.value().constBegin(); datesIter != processesIter.value().constEnd(); datesIter++)
{
logFiles++;
totalSize += datesIter.value().filesize;
}
QString sizeStr;
for (const QString prefix : { "K", "M", "G", "T" })
{
if (totalSize > 1024)
{
totalSize /= 1024;
sizeStr = QString::number(totalSize) % prefix;
}
}
m_labelSummary->setText(tr("Filters match %0 files (%1B)").arg(logFiles).arg(sizeStr));
}
ScanResult LocalImportPage::filterResult(ScanResult result) const
{
const auto processes = m_model.enabledItems().toSet();
for (auto hostsIter = result.begin(); hostsIter != result.end(); hostsIter++)
{
for (auto processesIter = hostsIter.value().begin(); processesIter != hostsIter.value().end(); )
{
if (processes.contains(processesIter.key()))
processesIter++;
else
processesIter = hostsIter.value().erase(processesIter);
}
}
return result;
}

39
wizard/localimportpage.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <QWizardPage>
#include "models/checklistmodel.h"
#include "common.h"
class QLineEdit;
class QDateEdit;
class QComboBox;
class QLabel;
class LocalImportPage : public QWizardPage
{
Q_OBJECT
public:
explicit LocalImportPage(QWidget *parent = nullptr);
int nextId() const override;
void initializePage() override;
bool validatePage() override;
private slots:
void updateSummary();
private:
ScanResult filterResult(ScanResult result) const;
ScanResult m_result;
ChecklistModel m_model;
QLineEdit *m_lineEditHost;
QDateEdit *m_dateEdit;
QComboBox *m_comboBox;
QLabel *m_labelSummary;
};

View File

@@ -0,0 +1,248 @@
#include "remoteimportoverviewpage.h"
#include <QVBoxLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QDateEdit>
#include <QComboBox>
#include <QListView>
#include <QMessageBox>
#include <QDir>
#include <QDirIterator>
#include <QVariant>
#include <QRegularExpression>
#include <QSet>
#include <QDate>
#include <QStringBuilder>
#include "importwizard.h"
#include "common.h"
RemoteImportOverviewPage::RemoteImportOverviewPage(QWidget *parent) :
QWizardPage(parent)
{
setTitle(tr("Remote Import Overview"));
setSubTitle(tr("TODO...."));
setCommitPage(true);
connect(&m_modelHosts, &ChecklistModel::dataChanged, this, &RemoteImportOverviewPage::updateSummary);
connect(&m_modelProcesses, &ChecklistModel::dataChanged, this, &RemoteImportOverviewPage::updateSummary);
auto layout = new QVBoxLayout;
{
auto groupBox = new QGroupBox(tr("Date-Range:"));
auto hboxLayout = new QHBoxLayout;
{
auto label = new QLabel(tr("From:"));
hboxLayout->addWidget(label);
}
m_dateEditFrom = new QDateEdit;
connect(m_dateEditFrom, &QDateTimeEdit::dateChanged, this, &RemoteImportOverviewPage::updateSummary);
hboxLayout->addWidget(m_dateEditFrom);
hboxLayout->addStretch(1);
{
auto label = new QLabel(tr("To:"));
hboxLayout->addWidget(label);
}
m_dateEditTo = new QDateEdit;
connect(m_dateEditTo, &QDateTimeEdit::dateChanged, this, &RemoteImportOverviewPage::updateSummary);
hboxLayout->addWidget(m_dateEditTo);
hboxLayout->addStretch(1);
{
auto label = new QLabel(tr("Timestamp:"));
hboxLayout->addWidget(label);
}
m_comboBox = new QComboBox;
m_comboBox->addItem(tr("Without milliseconds"), "HH:mm:ss");
m_comboBox->addItem(tr("With milliseconds"), "HH:mm:ss.zzz");
hboxLayout->addWidget(m_comboBox);
groupBox->setLayout(hboxLayout);
layout->addWidget(groupBox);
}
{
auto hboxLayout = new QHBoxLayout;
{
auto vboxLayout = new QVBoxLayout;
{
auto label = new QLabel(tr("Hosts:"));
vboxLayout->addWidget(label);
}
{
auto view = new QListView;
view->setModel(&m_modelHosts);
vboxLayout->addWidget(view, 1);
}
hboxLayout->addLayout(vboxLayout);
}
{
auto vboxLayout = new QVBoxLayout;
{
auto label = new QLabel(tr("Processes:"));
vboxLayout->addWidget(label);
}
{
auto view = new QListView;
view->setModel(&m_modelProcesses);
vboxLayout->addWidget(view, 1);
}
hboxLayout->addLayout(vboxLayout);
}
layout->addLayout(hboxLayout, 1);
}
m_labelSummary = new QLabel;
layout->addWidget(m_labelSummary);
setLayout(layout);
}
void RemoteImportOverviewPage::initializePage()
{
m_result = wizard()->property("result").value<ScanResult>();
QDate minDate, maxDate;
QSet<QString> processes;
for (auto hostsIter = m_result.constBegin(); hostsIter != m_result.constEnd(); hostsIter++)
for (auto processesIter = hostsIter.value().constBegin(); processesIter != hostsIter.value().constEnd(); processesIter++)
{
processes.insert(processesIter.key());
for (auto datesIter = processesIter.value().constBegin(); datesIter != processesIter.value().constEnd(); datesIter++)
{
if (minDate.isNull() || datesIter.key() < minDate)
minDate = datesIter.key();
if (maxDate.isNull() || datesIter.key() > maxDate)
maxDate = datesIter.key();
}
}
m_dateEditFrom->setDate(minDate);
m_dateEditTo->setDate(maxDate);
{
auto hosts = m_result.keys();
hosts.sort();
m_modelHosts.setItems(hosts);
}
{
auto processesList = processes.toList();
processesList.sort();
m_modelProcesses.setItems(processesList);
}
updateSummary();
}
int RemoteImportOverviewPage::nextId() const
{
return int(ImportWizard::Pages::ImportProgress);
}
bool RemoteImportOverviewPage::validatePage()
{
const auto result = filterResult(m_result);
if (scanResultEmpty(result))
{
QMessageBox::warning(this, tr("No files to import!"), tr("No files to import!"));
return false;
}
wizard()->setProperty("result", QVariant::fromValue(result));
wizard()->setProperty("timeFormat", m_comboBox->currentData().toString());
return true;
}
void RemoteImportOverviewPage::updateSummary()
{
if (m_result.isEmpty())
return;
const auto result = filterResult(m_result);
int logFiles { 0 };
qint64 totalSize { 0 };
for (auto hostsIter = result.constBegin(); hostsIter != result.constEnd(); hostsIter++)
for (auto processesIter = hostsIter.value().constBegin(); processesIter != hostsIter.value().constEnd(); processesIter++)
for (auto datesIter = processesIter.value().constBegin(); datesIter != processesIter.value().constEnd(); datesIter++)
{
logFiles++;
totalSize += datesIter.value().filesize;
}
QString sizeStr;
for (const QString prefix : { "K", "M", "G", "T" })
{
if (totalSize > 1024)
{
totalSize /= 1024;
sizeStr = QString::number(totalSize) % prefix;
}
}
m_labelSummary->setText(tr("Filters match %0 files (%1B)").arg(logFiles).arg(sizeStr));
}
ScanResult RemoteImportOverviewPage::filterResult(ScanResult result) const
{
const auto hosts = m_modelHosts.enabledItems().toSet();
const auto processes = m_modelProcesses.enabledItems().toSet();
for (auto hostsIter = result.begin(); hostsIter != result.end(); )
{
if (hosts.contains(hostsIter.key()))
{
for (auto processesIter = hostsIter.value().begin(); processesIter != hostsIter.value().end(); )
{
if (processes.contains(processesIter.key()))
{
for (auto datesIter = processesIter.value().begin(); datesIter != processesIter.value().end(); )
{
if (datesIter.key() >= m_dateEditFrom->date() && datesIter.key() <= m_dateEditTo->date())
datesIter++;
else
datesIter = processesIter.value().erase(datesIter);
}
processesIter++;
}
else
processesIter = hostsIter.value().erase(processesIter);
}
hostsIter++;
}
else
hostsIter = result.erase(hostsIter);
}
return result;
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include <QWizardPage>
#include "models/checklistmodel.h"
#include "common.h"
class QDateEdit;
class QComboBox;
class QLabel;
class RemoteImportOverviewPage : public QWizardPage
{
Q_OBJECT
public:
explicit RemoteImportOverviewPage(QWidget *parent = nullptr);
void initializePage() override;
int nextId() const override;
bool validatePage() override;
private slots:
void updateSummary();
private:
ScanResult filterResult(ScanResult result) const;
QDateEdit *m_dateEditFrom;
QDateEdit *m_dateEditTo;
QComboBox *m_comboBox;
QLabel *m_labelSummary;
ChecklistModel m_modelHosts;
ChecklistModel m_modelProcesses;
ScanResult m_result;
};

View File

@@ -0,0 +1,111 @@
#include "remoteimportscanpage.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPlainTextEdit>
#include "importwizard.h"
#include "threads/remotescannerthread.h"
RemoteImportScanPage::RemoteImportScanPage(QWidget *parent) :
QWizardPage(parent)
{
setTitle(tr("Remote Import Scan"));
setSubTitle(tr("TODO..."));
auto layout = new QVBoxLayout;
{
auto hboxLayout = new QHBoxLayout;
m_labelAnimation = new QLabel;
hboxLayout->addWidget(m_labelAnimation);
m_labelStatus = new QLabel;
hboxLayout->addWidget(m_labelStatus, 1);
layout->addLayout(hboxLayout);
}
m_logView = new QPlainTextEdit;
m_logView->setReadOnly(true);
layout->addWidget(m_logView, 1);
setLayout(layout);
}
RemoteImportScanPage::~RemoteImportScanPage() = default;
int RemoteImportScanPage::nextId() const
{
return int(ImportWizard::Pages::RemoteImportOverview);
}
void RemoteImportScanPage::initializePage()
{
wizard()->setProperty("result", QVariant::fromValue(ScanResult()));
m_labelAnimation->setMovie(&m_movieLoading);
m_movieLoading.start();
m_logView->clear();
m_thread = std::make_unique<RemoteScannerThread>(field("folder").toString(), this);
connect(m_thread.get(), &RemoteScannerThread::progressUpdate, this, &RemoteImportScanPage::progressUpdate);
connect(m_thread.get(), &RemoteScannerThread::logMessage, this, &RemoteImportScanPage::logMessage);
connect(m_thread.get(), &RemoteScannerThread::finished, this, &RemoteImportScanPage::finished);
m_thread->start();
}
void RemoteImportScanPage::cleanupPage()
{
if (m_thread)
{
m_thread->requestInterruption();
m_thread->wait();
m_thread = nullptr;
}
m_movieLoading.stop();
}
bool RemoteImportScanPage::isComplete() const
{
return m_thread == nullptr && !scanResultEmpty(wizard()->property("result").value<ScanResult>());
}
void RemoteImportScanPage::progressUpdate(int totalFiles, int skippedFiles)
{
m_labelStatus->setText(tr("%0 files scanned... (%1 files skipped)").arg(totalFiles).arg(skippedFiles));
}
void RemoteImportScanPage::logMessage(const QString &message)
{
m_logView->appendHtml(QString("%0: %1<br/>").arg(QTime::currentTime().toString(), message));
}
void RemoteImportScanPage::finished(const ScanResult &result)
{
m_labelAnimation->setMovie(nullptr);
const auto success = !scanResultEmpty(result);
if (success)
{
logMessage(tr("Finished"));
m_labelAnimation->setPixmap(m_pixmapSucceeded);
wizard()->setProperty("result", QVariant::fromValue(result));
}
else
{
logMessage(tr("Scan failed."));
m_labelAnimation->setPixmap(m_pixmapFailed);
}
cleanupPage();
if (success)
emit completeChanged();
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include <QWizardPage>
#include <QPixmap>
#include <QMovie>
#include <memory>
#include "common.h"
class QLabel;
class QPlainTextEdit;
class RemoteScannerThread;
class RemoteImportScanPage : public QWizardPage
{
Q_OBJECT
const QPixmap m_pixmapSucceeded { ":/loganalyzer/succeeded.png" };
const QPixmap m_pixmapFailed { ":/loganalyzer/failed.png" };
QMovie m_movieLoading { ":/loganalyzer/loading.gif" };
public:
explicit RemoteImportScanPage(QWidget *parent = nullptr);
~RemoteImportScanPage() override;
int nextId() const override;
void initializePage() override;
void cleanupPage() override;
bool isComplete() const override;
private slots:
void progressUpdate(int totalFiles, int skippedFiles);
void logMessage(const QString &message);
void finished(const ScanResult &result);
private:
std::unique_ptr<RemoteScannerThread> m_thread;
QLabel *m_labelAnimation;
QLabel *m_labelStatus;
QPlainTextEdit *m_logView;
};

89
wizard/tablespage.cpp Normal file
View File

@@ -0,0 +1,89 @@
#include "tablespage.h"
#include <QGridLayout>
#include <QLabel>
#include "importwizard.h"
#include "threads/tablecreatorthread.h"
TablesPage::TablesPage(QWidget *parent) :
QWizardPage(parent)
{
setTitle(tr("Tables"));
setSubTitle(tr("TODO..."));
auto layout = new QGridLayout;
m_statusLabels.resize(TableCreatorThread::tables().size());
int index { 0 };
for (const QString &tableName : TableCreatorThread::tables())
{
m_statusLabels[index] = new QLabel;
layout->addWidget(m_statusLabels[index], index, 0);
auto label = new QLabel(tr("Create table %0").arg(tableName));
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
layout->addWidget(label, index++, 1);
}
setLayout(layout);
}
TablesPage::~TablesPage() = default;
int TablesPage::nextId() const
{
return int(ImportWizard::Pages::ImportType);
}
void TablesPage::initializePage()
{
auto importWizard = qobject_cast<ImportWizard*>(wizard());
Q_ASSERT(importWizard);
Q_ASSERT(importWizard->database().isOpen());
for (auto label : m_statusLabels)
{
label->setMovie(nullptr);
label->setPixmap({});
}
m_thread = std::make_unique<TableCreatorThread>(importWizard->database(), this);
connect(m_thread.get(), &TableCreatorThread::someSignal, this, &TablesPage::someSlot);
m_thread->start();
m_statusLabels[0]->setMovie(&m_movieLoading);
m_movieLoading.start();
}
void TablesPage::cleanupPage()
{
if (m_thread)
{
m_thread->requestInterruption();
m_thread->wait();
m_thread = nullptr;
}
m_movieLoading.stop();
}
bool TablesPage::isComplete() const
{
return m_thread == nullptr;
}
void TablesPage::someSlot(int index)
{
Q_ASSERT(index < m_statusLabels.size());
m_statusLabels[index]->setMovie(nullptr);
m_statusLabels[index]->setPixmap(m_pixmapSucceeded);
if (index < m_statusLabels.size() - 1)
m_statusLabels[index+1]->setMovie(&m_movieLoading);
else
{
cleanupPage();
emit completeChanged();
}
}

40
wizard/tablespage.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <QWizardPage>
#include <QPixmap>
#include <QMovie>
#include <QVector>
#include <memory>
class QLabel;
class QSqlDatabase;
class TableCreatorThread;
class TablesPage : public QWizardPage
{
Q_OBJECT
const QPixmap m_pixmapSucceeded { ":/loganalyzer/succeeded.png" };
const QPixmap m_pixmapFailed { ":/loganalyzer/failed.png" };
QMovie m_movieLoading { ":/loganalyzer/loading.gif" };
public:
explicit TablesPage(QWidget *parent = nullptr);
~TablesPage() override;
int nextId() const override;
void initializePage() override;
void cleanupPage() override;
bool isComplete() const override;
private slots:
void someSlot(int index);
private:
QVector<QLabel*> m_statusLabels;
std::unique_ptr<TableCreatorThread> m_thread;
};