diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index 1c2539e08..eaf1c9035 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -36,6 +36,8 @@ #include "GZip.h" #include #include +#include +#include bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes) { @@ -136,3 +138,65 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes) } return true; } + +GZipStream::GZipStream(const QString& filePath) : GZipStream(new QFile(filePath)) {} + +GZipStream::GZipStream(QFile* file) : m_file(file) {} + +bool GZipStream::initStream() +{ + memset(&m_strm, 0, sizeof(m_strm)); + return (inflateInit2(&m_strm, 16 + MAX_WBITS) == Z_OK); +} + +bool GZipStream::unzipBlockByBlock(QByteArray& uncompressedBytes) +{ + uncompressedBytes.clear(); + if (!m_file->isOpen()) { + if (!m_file->open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open file:" << (m_file->fileName()); + return false; + } + } + + if (!m_strm.state && !initStream()) { + return false; + } + + QByteArray compressedBlock; + unsigned int blockSize = 4096; + + compressedBlock = m_file->read(blockSize); + if (compressedBlock.isEmpty()) { + return true; // End of file reached + } + + bool done = processBlock(compressedBlock, uncompressedBytes); + if (inflateEnd(&m_strm) != Z_OK || !done) { + return false; + } + return done; +} + +bool GZipStream::processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes) +{ + m_strm.next_in = (Bytef*)compressedBlock.data(); + m_strm.avail_in = compressedBlock.size(); + + unsigned int uncompLength = uncompressedBytes.size(); + if (m_strm.total_out >= uncompLength) { + uncompressedBytes.resize(uncompLength * 2); + uncompLength *= 2; + } + + m_strm.next_out = reinterpret_cast(uncompressedBytes.data() + m_strm.total_out); + m_strm.avail_out = uncompLength - m_strm.total_out; + + int err = inflate(&m_strm, Z_NO_FLUSH); + if (err != Z_OK && err != Z_STREAM_END) { + qWarning() << "Decompression failed with error code" << err; + return false; + } + + return true; +} diff --git a/launcher/GZip.h b/launcher/GZip.h index 0bdb70407..dd4162839 100644 --- a/launcher/GZip.h +++ b/launcher/GZip.h @@ -1,8 +1,28 @@ #pragma once +#include #include +#include class GZip { public: static bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes); static bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes); }; + +class GZipStream { + public: + explicit GZipStream(const QString& filePath); + explicit GZipStream(QFile* file); + + // Decompress the next block and return the decompressed data + bool unzipBlockByBlock(QByteArray& uncompressedBytes); + + private: + bool initStream(); + + bool processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes); + + private: + QFile* m_file; + z_stream m_strm; +}; diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index dd32d46a2..45aac6099 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -161,3 +161,8 @@ bool LogModel::colorLines() const { return m_colorLines; } + +bool LogModel::isOverFlow() +{ + return m_numLines >= m_maxLines && m_stopOnOverflow; +} diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index 6c2a8cff3..ba7b14487 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -24,6 +24,7 @@ class LogModel : public QAbstractListModel { void setMaxLines(int maxLines); void setStopOnOverflow(bool stop); void setOverflowMessage(const QString& overflowMessage); + bool isOverFlow(); void setLineWrap(bool state); bool wrapLines() const; diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 0d94843c0..b7d10a41a 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -151,6 +151,48 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) } } +class ReadLineAbstract { + public: + ReadLineAbstract(QFile* file) : m_file(file) + { + if (file->fileName().endsWith(".gz")) + m_gz = new GZipStream(file); + } + ~ReadLineAbstract() { delete m_gz; } + + QString readLine() + { + if (!m_gz) + return QString::fromUtf8(m_file->readLine()); + QString line; + for (;;) { + if (!m_decodedData.isEmpty()) { + int newlineIndex = m_decodedData.indexOf('\n'); + if (newlineIndex != -1) { + line += QString::fromUtf8(m_decodedData).left(newlineIndex); + m_decodedData.remove(0, newlineIndex + 1); + return line; + } + + line += QString::fromUtf8(m_decodedData); + m_decodedData.clear(); + } + + if (!m_gz->unzipBlockByBlock(m_decodedData)) { // If error occurs during unzipping + m_decodedData.clear(); + return QObject::tr("The content of the file(%1) could not be decoded.").arg(m_file->fileName()); + } + } + } + + bool done() { return m_gz ? m_decodedData.isEmpty() : m_file->atEnd(); } + + private: + QFile* m_file; + GZipStream* m_gz = nullptr; + QByteArray m_decodedData; +}; + void OtherLogsPage::on_btnReload_clicked() { if (m_currentFile.isEmpty()) { @@ -178,35 +220,17 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - QString content; - if (file.fileName().endsWith(".gz")) { - QByteArray temp; - if (!GZip::unzip(file.readAll(), temp)) { - setPlainText(tr("The file (%1) is not readable.").arg(file.fileName())); - return; - } - content = QString::fromUtf8(temp); - } else { - content = QString::fromUtf8(file.readAll()); - } - if (content.size() >= 50000000ll) { - showTooBig(); - return; - } - // If the file is not too big for display, but too slow for syntax highlighting, just show content as plain text - if (content.size() >= 10000000ll || content.isEmpty()) { - setPlainText(content); - return; - } + ReadLineAbstract stream(&file); // Try to determine a level for each line - if (content.back() == '\n') - content = content.remove(content.size() - 1, 1); ui->text->clear(); ui->text->setModel(nullptr); m_model->clear(); - for (auto& line : content.split('\n')) { + auto line = stream.readLine(); + while (!stream.done()) { // just read until the model is full or the file ended + if (line.back() == '\n') + line = line.remove(line.size() - 1, 1); MessageLevel::Enum level = MessageLevel::Unknown; // if the launcher part set a log level, use it @@ -221,6 +245,10 @@ void OtherLogsPage::on_btnReload_clicked() } m_model->append(level, line); + if (m_model->isOverFlow()) + break; + + line = stream.readLine(); } ui->text->setModel(m_proxy); ui->text->scrollToBottom(); diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 181893af4..df25a2434 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -42,6 +42,7 @@ LogView::LogView(QWidget* parent) : QPlainTextEdit(parent) { setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); m_defaultFormat = new QTextCharFormat(currentCharFormat()); + setUndoRedoEnabled(false); } LogView::~LogView() @@ -129,6 +130,8 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) QTextDocument document; QTextCursor cursor(&document); + cursor.movePosition(QTextCursor::End); + cursor.beginEditBlock(); for (int i = first; i <= last; i++) { auto idx = m_model->index(i, 0, parent); auto text = m_model->data(idx, Qt::DisplayRole).toString(); @@ -145,10 +148,10 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) if (bg.isValid() && m_colorLines) { format.setBackground(bg.value()); } - cursor.movePosition(QTextCursor::End); cursor.insertText(text, format); cursor.insertBlock(); } + cursor.endEditBlock(); QTextDocumentFragment fragment(&document); QTextCursor workCursor = textCursor();