From cd9bfb8ffe98f7c3518c61afe2c358347ea95ebd Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Mon, 1 Apr 2019 10:43:36 +0200 Subject: [PATCH] Imported existing sources --- common.h | 30 +++ dialogs/graphdialog.cpp | 104 ++++++++ dialogs/graphdialog.h | 23 ++ dialogs/graphdialog.ui | 55 +++++ dialogs/opendialog.cpp | 36 +++ dialogs/opendialog.h | 27 +++ dialogs/opendialog.ui | 59 +++++ failed.png | Bin 0 -> 991 bytes gzipdevice.cpp | 77 ++++++ gzipdevice.h | 28 +++ loading.gif | Bin 0 -> 1737 bytes loganalyzer.pro | 76 ++++++ main.cpp | 17 ++ mainwindow.cpp | 217 +++++++++++++++++ mainwindow.h | 38 +++ mainwindow.ui | 216 +++++++++++++++++ models/checklistmodel.cpp | 141 +++++++++++ models/checklistmodel.h | 33 +++ models/sqlrelationaltablemodel.cpp | 14 ++ models/sqlrelationaltablemodel.h | 13 + resources.qrc | 7 + succeeded.png | Bin 0 -> 1197 bytes threads/importthread.cpp | 353 ++++++++++++++++++++++++++++ threads/importthread.h | 63 +++++ threads/remotescannerthread.cpp | 86 +++++++ threads/remotescannerthread.h | 40 ++++ threads/tablecreatorthread.cpp | 101 ++++++++ threads/tablecreatorthread.h | 26 ++ widgets/databasewidget.cpp | 104 ++++++++ widgets/databasewidget.h | 40 ++++ widgets/databasewidget.ui | 124 ++++++++++ widgets/fileselectionwidget.cpp | 75 ++++++ widgets/fileselectionwidget.h | 38 +++ widgets/fileselectionwidget.ui | 31 +++ wizard/conclusionpage.cpp | 54 +++++ wizard/conclusionpage.h | 20 ++ wizard/databasepage.cpp | 46 ++++ wizard/databasepage.h | 23 ++ wizard/databasepage.ui | 51 ++++ wizard/importprogresspage.cpp | 117 +++++++++ wizard/importprogresspage.h | 46 ++++ wizard/importtypepage.cpp | 117 +++++++++ wizard/importtypepage.h | 27 +++ wizard/importwizard.cpp | 35 +++ wizard/importwizard.h | 20 ++ wizard/intropage.cpp | 18 ++ wizard/intropage.h | 21 ++ wizard/intropage.ui | 34 +++ wizard/localimportpage.cpp | 161 +++++++++++++ wizard/localimportpage.h | 39 +++ wizard/remoteimportoverviewpage.cpp | 248 +++++++++++++++++++ wizard/remoteimportoverviewpage.h | 40 ++++ wizard/remoteimportscanpage.cpp | 111 +++++++++ wizard/remoteimportscanpage.h | 45 ++++ wizard/tablespage.cpp | 89 +++++++ wizard/tablespage.h | 40 ++++ 56 files changed, 3694 insertions(+) create mode 100644 common.h create mode 100644 dialogs/graphdialog.cpp create mode 100644 dialogs/graphdialog.h create mode 100644 dialogs/graphdialog.ui create mode 100644 dialogs/opendialog.cpp create mode 100644 dialogs/opendialog.h create mode 100644 dialogs/opendialog.ui create mode 100644 failed.png create mode 100644 gzipdevice.cpp create mode 100644 gzipdevice.h create mode 100644 loading.gif create mode 100644 loganalyzer.pro create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui create mode 100644 models/checklistmodel.cpp create mode 100644 models/checklistmodel.h create mode 100644 models/sqlrelationaltablemodel.cpp create mode 100644 models/sqlrelationaltablemodel.h create mode 100644 resources.qrc create mode 100644 succeeded.png create mode 100644 threads/importthread.cpp create mode 100644 threads/importthread.h create mode 100644 threads/remotescannerthread.cpp create mode 100644 threads/remotescannerthread.h create mode 100644 threads/tablecreatorthread.cpp create mode 100644 threads/tablecreatorthread.h create mode 100644 widgets/databasewidget.cpp create mode 100644 widgets/databasewidget.h create mode 100644 widgets/databasewidget.ui create mode 100644 widgets/fileselectionwidget.cpp create mode 100644 widgets/fileselectionwidget.h create mode 100644 widgets/fileselectionwidget.ui create mode 100644 wizard/conclusionpage.cpp create mode 100644 wizard/conclusionpage.h create mode 100644 wizard/databasepage.cpp create mode 100644 wizard/databasepage.h create mode 100644 wizard/databasepage.ui create mode 100644 wizard/importprogresspage.cpp create mode 100644 wizard/importprogresspage.h create mode 100644 wizard/importtypepage.cpp create mode 100644 wizard/importtypepage.h create mode 100644 wizard/importwizard.cpp create mode 100644 wizard/importwizard.h create mode 100644 wizard/intropage.cpp create mode 100644 wizard/intropage.h create mode 100644 wizard/intropage.ui create mode 100644 wizard/localimportpage.cpp create mode 100644 wizard/localimportpage.h create mode 100644 wizard/remoteimportoverviewpage.cpp create mode 100644 wizard/remoteimportoverviewpage.h create mode 100644 wizard/remoteimportscanpage.cpp create mode 100644 wizard/remoteimportscanpage.h create mode 100644 wizard/tablespage.cpp create mode 100644 wizard/tablespage.h diff --git a/common.h b/common.h new file mode 100644 index 0000000..27efad1 --- /dev/null +++ b/common.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +using Host = QString; +using Process = QString; +using Filename = QString; +struct Logfile { + QString filename; + QString filepath; + qint64 filesize; + bool gzipCompressed; +}; +using ScanResult = QHash > >; +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(); + }); + }); +} diff --git a/dialogs/graphdialog.cpp b/dialogs/graphdialog.cpp new file mode 100644 index 0000000..8105df0 --- /dev/null +++ b/dialogs/graphdialog.cpp @@ -0,0 +1,104 @@ +#include "graphdialog.h" +#include "ui_graphdialog.h" + +#include +#include +#include +#include + +#include +#include +#include + +GraphDialog::GraphDialog(QSqlDatabase &database, QWidget *parent) : + QDialog(parent), + m_ui(std::make_unique()), + 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; diff --git a/dialogs/graphdialog.h b/dialogs/graphdialog.h new file mode 100644 index 0000000..1f6160e --- /dev/null +++ b/dialogs/graphdialog.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +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 m_ui; + + QSqlDatabase &m_database; +}; diff --git a/dialogs/graphdialog.ui b/dialogs/graphdialog.ui new file mode 100644 index 0000000..f6aaaaf --- /dev/null +++ b/dialogs/graphdialog.ui @@ -0,0 +1,55 @@ + + + GraphDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + + QDialogButtonBox::Close + + + + + + + + QtCharts::QChartView + QGraphicsView +
qchartview.h
+
+
+ + + + buttonBox + rejected() + GraphDialog + close() + + + 199 + 278 + + + 199 + 149 + + + + +
diff --git a/dialogs/opendialog.cpp b/dialogs/opendialog.cpp new file mode 100644 index 0000000..086b3a1 --- /dev/null +++ b/dialogs/opendialog.cpp @@ -0,0 +1,36 @@ +#include "opendialog.h" +#include "ui_opendialog.h" + +#include +#include +#include + +OpenDialog::OpenDialog(QWidget *parent) : + QDialog(parent), + m_ui(std::make_unique()) +{ + 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(); +} diff --git a/dialogs/opendialog.h b/dialogs/opendialog.h new file mode 100644 index 0000000..afa94b4 --- /dev/null +++ b/dialogs/opendialog.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include + +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 m_ui; + + QSqlDatabase m_database; +}; diff --git a/dialogs/opendialog.ui b/dialogs/opendialog.ui new file mode 100644 index 0000000..f4e8e2b --- /dev/null +++ b/dialogs/opendialog.ui @@ -0,0 +1,59 @@ + + + OpenDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + DatabaseWidget + QWidget +
widgets/databasewidget.h
+ 1 +
+
+ + + + buttonBox + rejected() + OpenDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/failed.png b/failed.png new file mode 100644 index 0000000000000000000000000000000000000000..5efd28341c75d9c8e8c5e5dcfa2734e740829521 GIT binary patch literal 991 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7*pj^6T^PXNr}OT51_lPs z0*}aI1_s{iAk65bF}ngJSXSVe4$=$8F~`;(W?*1)_jGX#u{gan*wO!ygG6oct_jn& zNi^^7p7Qj-J)a(T{p{#Hg%7r{*C(CdD=3`%^-1HA2@?Kt4iVdwdbdRA%zgRhU~31v ze%%yXow%$^@yz+6>wPOWBEPjF4qc^SF?jf+IP>|tAN|N^0S&`Ou(;PV1=Jr{>|1Nud z(=CREcJ}2F>t|PO)@&^1v6403wSPO~A2CLS#UeM0I&(HUh_zkdb(WelZ`Q?v7Yq%n zUN6g&5c*onG3(o@R2~Kgi{}NLf|uP62WpFMzL(<_DdKp+g{i)XkBvbY|6 z@J5I4c=G$xcVllqnKLPrOHVN=XuWmT(xg8YR+BdWl(7tDW!PZQvS5`&Ow5ss6>mg; z{oQubGB$sMS+_vp!qoRk5PsAHS1`;jUe?+81A3Y9g~vKmW)MEjP^tWI|Q7r&Ou*xOazO4+1& zP}Qa8*1fVPYjq$0{-L1P+nfHmzkGcoSayY3eqKQSqvoE{_-(7z`28@u5LJaA)MFr0S*T=cnhS>Lusr>KEvz=jG?7>L!Er U>IcYfeGST}p00i_>zopr01;x3xBvhE literal 0 HcmV?d00001 diff --git a/gzipdevice.cpp b/gzipdevice.cpp new file mode 100644 index 0000000..700b254 --- /dev/null +++ b/gzipdevice.cpp @@ -0,0 +1,77 @@ +#include "gzipdevice.h" + +#include + +#include + +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(m_readBuffer); + strm.avail_in = m_file.read(m_readBuffer, m_readBufferSize); + } + + strm.next_out = reinterpret_cast(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; +} diff --git a/gzipdevice.h b/gzipdevice.h new file mode 100644 index 0000000..05780c1 --- /dev/null +++ b/gzipdevice.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#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; +}; diff --git a/loading.gif b/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..1560b646cff2cc4fd292d7fdb6f7adc7eb484b4a GIT binary patch literal 1737 zcmZ?wbhEHb6krfw_{PBS|Nnmm28JtFu1uLSB_Scf!NI}B#U(ERY-`0?Y~wQK9v zt^4rd!=+1?o;-PS@7}$0=gu8Fc5Kg{Jr5o{xN+mgr%#`5-MUp(RkdZymQ9;B`S|z* z1qDS#MI|LAIXOAm*w`F7a-^)R%*e>-)vH%pT3UU5eGw56IyyRXa&ik6Eb#O56A}{Y z=;%mENii`oIdS5|nKNgeJ$tre$Bw$Xx=E8J&6_tbEG%rogb7|=ULGDEN=i!0mMs$y z5a8kAS+iyh3k%DvS+msC)I>x?EG#TaN=o$f^f)*;3JMAo6cj>2LUM9)8X6iHU_kLd zx1VcBu(M-;tC5}oGa~~7gW^9a=c3falGGH1^30M9g~Y7Hik$q!6ur#6w0s7|pDdhQ z3=9l9AQM25$iUv)z|hp((%LGmtf|}9$|0korPpL;W!}ogEvu@n-(+rJYu3!fCM4Hp zCL%J8ftg#lhr!m~wuyyDaHFw~Q3}{F;o(BJzJx{g>zE;i)}P3FxW z-hrDLtjwBNd~A2QxizW#9@#7++9dAhxY;V{G=rxA$L0`+gADfKK~in{OfGwyER9S& zIjyYpT@$1)H#@3@=|stUIVXlp2(k3cS|c!tp>16=0}~@Q2X;ao$jK#~$rh)>)W+b< z?#;%}$IFwI(`pfyo)IW2>Kv5K!_lmi;%C#Ap&KCGlq`@ofk7ZQwn<0WZo5}rR+Ev3 z!S)b6-6lB`lPFn^`rB$fxaZwX!|m>~1RW7-DX2Afhj7*6QG_;q2sS z?Vx|B&5cvK=!b&P`lziA*j(2Sb)CJ2oPeKVj(Zz}r8v8~WO{U#pI57+vPX`zo2^qU zzrRg$h?PXqtu8iPVh+Z}OkAo0Qq67_Hr8!k3c?Oe z4$0CJ7*eBin(Qn>wmW5&HYGZlZx`kF-s5P(A20QkQ-JABi z#j!Vu_8;?AZ!&N*-|ys;(qv|Qc|vSfPP6&#RyBS<_cj*okdS1bK^A=0@EBfnN4VN(XrXnYAly}u*KL!u8B?Wzyu~kwI(C2xIIzq zf=$wVG2(4Del@e3qOx@>l0`(t!m8pdS~>hHi^}-;v#OoXr%Lj(@hNyDoQY7t7Ps9H z$F-STxrvIpnYA^W8M}!jxwzQ2s>@O?; literal 0 HcmV?d00001 diff --git a/loganalyzer.pro b/loganalyzer.pro new file mode 100644 index 0000000..cf8e3f6 --- /dev/null +++ b/loganalyzer.pro @@ -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 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..911ee4b --- /dev/null +++ b/main.cpp @@ -0,0 +1,17 @@ +#include +#include + +#include "mainwindow.h" +#include "common.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + qRegisterMetaType(); + + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..770bb42 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,217 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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()) +{ + 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(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(); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..22ec2ab --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include + +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 m_ui; + + QSqlDatabase m_database; + std::unique_ptr m_model; +}; diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..d882efc --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,216 @@ + + + MainWindow + + + + 0 + 0 + 965 + 578 + + + + MainWindow + + + + + + + + + false + + + + + + + + + + false + + + Filter + + + + + + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + false + + + + + + + + + 0 + 0 + 965 + 20 + + + + + &File + + + + + + + + + + &View + + + + + + + + + + + + &Tools + + + + + + + + + + + &New project + + + + + &Open + + + + + Quit + + + + + Close Database + + + false + + + + + true + + + true + + + Timestamp + + + + + true + + + true + + + Host + + + + + true + + + true + + + Process + + + + + true + + + false + + + Filename + + + + + true + + + true + + + Thread + + + + + true + + + true + + + Type + + + + + true + + + true + + + Message + + + + + false + + + &Graph + + + + + Raw data + + + + + + + diff --git a/models/checklistmodel.cpp b/models/checklistmodel.cpp new file mode 100644 index 0000000..dc451a4 --- /dev/null +++ b/models/checklistmodel.cpp @@ -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 > &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 > &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 ChecklistModel::itemData(const QModelIndex &index) const +{ + if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) + return QMap{}; + const auto &item = m_items.at(index.row()); + return QMap{{ + std::make_pair(Qt::DisplayRole, std::get<0>(item)), + std::make_pair(Qt::EditRole, std::get<0>(item)), + std::make_pair(Qt::CheckStateRole, std::get<1>(item) ? Qt::Checked : Qt::Unchecked) + }}; +} + +bool ChecklistModel::setItemData(const QModelIndex &index, const QMap &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; +} diff --git a/models/checklistmodel.h b/models/checklistmodel.h new file mode 100644 index 0000000..f75401f --- /dev/null +++ b/models/checklistmodel.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include + +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 > &strings, QObject *parent = nullptr); + + QStringList items() const; + void setItems(const QStringList &items); + void setItems(const QList > &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 itemData(const QModelIndex &index) const override; + bool setItemData(const QModelIndex &index, const QMap &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 > m_items; +}; diff --git a/models/sqlrelationaltablemodel.cpp b/models/sqlrelationaltablemodel.cpp new file mode 100644 index 0000000..ff5d3d2 --- /dev/null +++ b/models/sqlrelationaltablemodel.cpp @@ -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); +} diff --git a/models/sqlrelationaltablemodel.h b/models/sqlrelationaltablemodel.h new file mode 100644 index 0000000..03e7105 --- /dev/null +++ b/models/sqlrelationaltablemodel.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +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; +}; diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..872bcc9 --- /dev/null +++ b/resources.qrc @@ -0,0 +1,7 @@ + + + loading.gif + failed.png + succeeded.png + + diff --git a/succeeded.png b/succeeded.png new file mode 100644 index 0000000000000000000000000000000000000000..8b6f7a475633480cad1c578f38060f1ab5fedc9f GIT binary patch literal 1197 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7SkfJR9T^zbpD<_bdda}R zz?S6g?!o{DKb?2iGcYi47I;J!GcYiCfU(=jY&(!(S%G6Z$Sg39IkxsN0|V1iPZ!4! zi_=RddwWa?lsS5S@AvB7&jliLGZ(SWDqNho(2{(Vt(phRU{^t($q>-bC$@^2sz;r)i#@nGBuxEfjSE zAAhm5`nHC*)hvA4<0EU;cgT0YSN5y&)zmArH{V$?vo7B>tiMqr0aioVZ)Fz%VFCevO;o zl8YZ-Xzp6Cl6PoJx#RXt(~=Hd`!rz}>oeZzwQ}=MoYSA19%9}WE~q@=YtGu0LC&pD zXKWDKVP(;|o6r@<*j$3Jn|bN&`<^lbCA z?%ch;?ZVxks(Qyys`;uHUcIy+lC5VC_puU18A<(!!lySX6k-(Mw5jCQ zhoxDLWqU|>)!ag8WRE=o-- zN!3kBEJXMgOP!ufv$m( zuAzB|fw`5Tk(H4-L<7UL#$PBJa`RI%(<-4F40R0+Lkujf3@xmTp&Aw>dA(s^U@!$a z$tN>8HLp08A)} zt!bA=jmo==B4W;=jZAd6y;~7CYKcJ X=Yh5Bm)ULs6-W%8u6{1-oD!M<@ov*h literal 0 HcmV?d00001 diff --git a/threads/importthread.cpp b/threads/importthread.cpp new file mode 100644 index 0000000..b2e4270 --- /dev/null +++ b/threads/importthread.cpp @@ -0,0 +1,353 @@ +#include "importthread.h" + +#include +#include +#include +#include + +#include + +#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 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 gzipProxy([&datesIter,&file](){ + if (datesIter.value().gzipCompressed) + return std::make_unique(file); + return std::unique_ptr{}; + }()); + + QTextStream textStream(gzipProxy != nullptr ? static_cast(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; +} diff --git a/threads/importthread.h b/threads/importthread.h new file mode 100644 index 0000000..d60d5ac --- /dev/null +++ b/threads/importthread.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +#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 m_hosts; + QHash m_processes; + QHash m_filenames; + QHash m_threads; + QHash 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: (.*) -----$" }; +}; diff --git a/threads/remotescannerthread.cpp b/threads/remotescannerthread.cpp new file mode 100644 index 0000000..7d9453a --- /dev/null +++ b/threads/remotescannerthread.cpp @@ -0,0 +1,86 @@ +#include "remotescannerthread.h" + +#include +#include +#include + +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; + } + } +} diff --git a/threads/remotescannerthread.h b/threads/remotescannerthread.h new file mode 100644 index 0000000..c0191ae --- /dev/null +++ b/threads/remotescannerthread.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include + +#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; +}; diff --git a/threads/tablecreatorthread.cpp b/threads/tablecreatorthread.cpp new file mode 100644 index 0000000..f09ac93 --- /dev/null +++ b/threads/tablecreatorthread.cpp @@ -0,0 +1,101 @@ +#include "tablecreatorthread.h" + +#include +#include +#include +#include + +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++); + } +} diff --git a/threads/tablecreatorthread.h b/threads/tablecreatorthread.h new file mode 100644 index 0000000..92c4548 --- /dev/null +++ b/threads/tablecreatorthread.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +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; +}; diff --git a/widgets/databasewidget.cpp b/widgets/databasewidget.cpp new file mode 100644 index 0000000..bf7eae1 --- /dev/null +++ b/widgets/databasewidget.cpp @@ -0,0 +1,104 @@ +#include "databasewidget.h" +#include "ui_databasewidget.h" + +DatabaseWidget::DatabaseWidget(QWidget *parent) : + QWidget(parent), + m_ui(std::make_unique()) +{ + 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; +} diff --git a/widgets/databasewidget.h b/widgets/databasewidget.h new file mode 100644 index 0000000..59b7dd5 --- /dev/null +++ b/widgets/databasewidget.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +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 m_ui; +}; diff --git a/widgets/databasewidget.ui b/widgets/databasewidget.ui new file mode 100644 index 0000000..d9e9e4f --- /dev/null +++ b/widgets/databasewidget.ui @@ -0,0 +1,124 @@ + + + DatabaseWidget + + + + 0 + 0 + 400 + 175 + + + + Form + + + + + + + + + 0 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + <b>Hostname:</b> + + + + + + + <b>Username:</b> + + + + + + + <b>Password:</b> + + + + + + + <b>Database:</b> + + + + + + + QLineEdit::Password + + + + + + + + + + + + + + + FileSelectionWidget + QWidget +
widgets/fileselectionwidget.h
+ 1 +
+
+ + + + comboBox + currentIndexChanged(int) + stackedWidget + setCurrentIndex(int) + + + 199 + 20 + + + 199 + 101 + + + + +
diff --git a/widgets/fileselectionwidget.cpp b/widgets/fileselectionwidget.cpp new file mode 100644 index 0000000..e67f6aa --- /dev/null +++ b/widgets/fileselectionwidget.cpp @@ -0,0 +1,75 @@ +#include "fileselectionwidget.h" +#include "ui_fileselectionwidget.h" + +#include + +FileSelectionWidget::FileSelectionWidget(QWidget *parent) : + QWidget(parent), + m_ui(std::make_unique()), + 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()), + 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()), + 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); +} diff --git a/widgets/fileselectionwidget.h b/widgets/fileselectionwidget.h new file mode 100644 index 0000000..320fbfa --- /dev/null +++ b/widgets/fileselectionwidget.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include + +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 m_ui; + Mode m_mode; +}; diff --git a/widgets/fileselectionwidget.ui b/widgets/fileselectionwidget.ui new file mode 100644 index 0000000..bf2b6e5 --- /dev/null +++ b/widgets/fileselectionwidget.ui @@ -0,0 +1,31 @@ + + + FileSelectionWidget + + + + 0 + 0 + 400 + 41 + + + + Form + + + + + + + + + Select... + + + + + + + + diff --git a/wizard/conclusionpage.cpp b/wizard/conclusionpage.cpp new file mode 100644 index 0000000..6634487 --- /dev/null +++ b/wizard/conclusionpage.cpp @@ -0,0 +1,54 @@ +#include "conclusionpage.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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("%0 rows have been imported.").arg(query.value(0).toInt())); +} diff --git a/wizard/conclusionpage.h b/wizard/conclusionpage.h new file mode 100644 index 0000000..3a9e215 --- /dev/null +++ b/wizard/conclusionpage.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +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; +}; diff --git a/wizard/databasepage.cpp b/wizard/databasepage.cpp new file mode 100644 index 0000000..5ffb0d0 --- /dev/null +++ b/wizard/databasepage.cpp @@ -0,0 +1,46 @@ +#include "databasepage.h" +#include "ui_databasepage.h" + +#include +#include +#include +#include +#include +#include + +#include "widgets/databasewidget.h" +#include "importwizard.h" + +DatabasePage::DatabasePage(QWidget *parent) : + QWizardPage(parent), + m_ui(std::make_unique()) +{ + 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(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; +} diff --git a/wizard/databasepage.h b/wizard/databasepage.h new file mode 100644 index 0000000..5459b4f --- /dev/null +++ b/wizard/databasepage.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +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 m_ui; +}; diff --git a/wizard/databasepage.ui b/wizard/databasepage.ui new file mode 100644 index 0000000..9e7024f --- /dev/null +++ b/wizard/databasepage.ui @@ -0,0 +1,51 @@ + + + DatabasePage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Database + + + Please setup the database connection. + + + + + + + + + Qt::Vertical + + + + 20 + 263 + + + + + + + + + DatabaseWidget + QWidget +
widgets/databasewidget.h
+ 1 +
+
+ + +
diff --git a/wizard/importprogresspage.cpp b/wizard/importprogresspage.cpp new file mode 100644 index 0000000..1f601ba --- /dev/null +++ b/wizard/importprogresspage.cpp @@ -0,0 +1,117 @@ +#include "importprogresspage.h" + +#include +#include +#include +#include +#include + +#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(wizard()); + Q_ASSERT(importWizard); + Q_ASSERT(importWizard->database().isOpen()); + + const auto result = wizard()->property("result").value(); + const auto timeFormat = wizard()->property("timeFormat").toString(); + + m_logView->clear(); + + m_thread = std::make_unique(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
").arg(QTime::currentTime().toString(), message)); +} + +void ImportProgressPage::finished() +{ + cleanupPage(); + emit completeChanged(); + + m_labelIcon->setPixmap(m_pixmapSucceeded); + logMessage(tr("Finished.")); +} diff --git a/wizard/importprogresspage.h b/wizard/importprogresspage.h new file mode 100644 index 0000000..3c52fb0 --- /dev/null +++ b/wizard/importprogresspage.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +#include + +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 m_thread; +}; diff --git a/wizard/importtypepage.cpp b/wizard/importtypepage.cpp new file mode 100644 index 0000000..9a5df1d --- /dev/null +++ b/wizard/importtypepage.cpp @@ -0,0 +1,117 @@ +#include "importtypepage.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/wizard/importtypepage.h b/wizard/importtypepage.h new file mode 100644 index 0000000..b2014e1 --- /dev/null +++ b/wizard/importtypepage.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +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; +}; diff --git a/wizard/importwizard.cpp b/wizard/importwizard.cpp new file mode 100644 index 0000000..f6339a8 --- /dev/null +++ b/wizard/importwizard.cpp @@ -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; +} diff --git a/wizard/importwizard.h b/wizard/importwizard.h new file mode 100644 index 0000000..4eaa4ef --- /dev/null +++ b/wizard/importwizard.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +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; +}; diff --git a/wizard/intropage.cpp b/wizard/intropage.cpp new file mode 100644 index 0000000..157975d --- /dev/null +++ b/wizard/intropage.cpp @@ -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()) +{ + m_ui->setupUi(this); +} + +IntroPage::~IntroPage() = default; + +int IntroPage::nextId() const +{ + return int(ImportWizard::Pages::Database); +} diff --git a/wizard/intropage.h b/wizard/intropage.h new file mode 100644 index 0000000..851c23d --- /dev/null +++ b/wizard/intropage.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +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 m_ui; +}; diff --git a/wizard/intropage.ui b/wizard/intropage.ui new file mode 100644 index 0000000..045bcac --- /dev/null +++ b/wizard/intropage.ui @@ -0,0 +1,34 @@ + + + IntroPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Introduction + + + TODO... + + + + + + TODO: long introduction... + + + + + + + + diff --git a/wizard/localimportpage.cpp b/wizard/localimportpage.cpp new file mode 100644 index 0000000..b7448ca --- /dev/null +++ b/wizard/localimportpage.cpp @@ -0,0 +1,161 @@ +#include "localimportpage.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(); + + 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; +} diff --git a/wizard/localimportpage.h b/wizard/localimportpage.h new file mode 100644 index 0000000..cd1718f --- /dev/null +++ b/wizard/localimportpage.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#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; +}; diff --git a/wizard/remoteimportoverviewpage.cpp b/wizard/remoteimportoverviewpage.cpp new file mode 100644 index 0000000..d45619b --- /dev/null +++ b/wizard/remoteimportoverviewpage.cpp @@ -0,0 +1,248 @@ +#include "remoteimportoverviewpage.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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(); + + QDate minDate, maxDate; + QSet 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; +} diff --git a/wizard/remoteimportoverviewpage.h b/wizard/remoteimportoverviewpage.h new file mode 100644 index 0000000..e61833a --- /dev/null +++ b/wizard/remoteimportoverviewpage.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#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; +}; diff --git a/wizard/remoteimportscanpage.cpp b/wizard/remoteimportscanpage.cpp new file mode 100644 index 0000000..6c0b981 --- /dev/null +++ b/wizard/remoteimportscanpage.cpp @@ -0,0 +1,111 @@ +#include "remoteimportscanpage.h" + +#include +#include +#include +#include + +#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(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()); +} + +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
").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(); +} diff --git a/wizard/remoteimportscanpage.h b/wizard/remoteimportscanpage.h new file mode 100644 index 0000000..b3f06a6 --- /dev/null +++ b/wizard/remoteimportscanpage.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include + +#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 m_thread; + + QLabel *m_labelAnimation; + QLabel *m_labelStatus; + QPlainTextEdit *m_logView; +}; diff --git a/wizard/tablespage.cpp b/wizard/tablespage.cpp new file mode 100644 index 0000000..092f2aa --- /dev/null +++ b/wizard/tablespage.cpp @@ -0,0 +1,89 @@ +#include "tablespage.h" + +#include +#include + +#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(wizard()); + Q_ASSERT(importWizard); + Q_ASSERT(importWizard->database().isOpen()); + + for (auto label : m_statusLabels) + { + label->setMovie(nullptr); + label->setPixmap({}); + } + + m_thread = std::make_unique(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(); + } +} diff --git a/wizard/tablespage.h b/wizard/tablespage.h new file mode 100644 index 0000000..1d46928 --- /dev/null +++ b/wizard/tablespage.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +#include + +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 m_statusLabels; + + std::unique_ptr m_thread; +};