aboutsummaryrefslogtreecommitdiffstats
//=============================================================================
//
//   File : LogViewWindow.cpp
//   Creation date : Tue Apr 23 2002 18:08:22 by Juanjo Alvarez
//
//   This file is part of the KVIrc IRC client distribution
//   Copyright (C) 2002 Juanjo Alvarez
//   Copyright (C) 2002-2010 Szymon Stefanek (pragma at kvirc dot net)
//   Copyright (C) 2011 Elvio Basello (hellvis69 at gmail dot com)
//   Copyright (C) 2014 OmegaPhil (OmegaPhil at startmail dot com)
//
//   This program is FREE software. You can redistribute it and/or
//   modify it under the terms of the GNU General Public License
//   as published by the Free Software Foundation; either version 2
//   of the License, or (at your option) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

#include "LogViewWindow.h"
#include "LogViewWidget.h"

#include "KviHtmlGenerator.h"
#include "KviIconManager.h"
#include "KviLocale.h"
#include "KviModule.h"
#include "KviOptions.h"
#include "KviMainWindow.h"
#include "KviIrcView.h"
#include "KviQString.h"
#include "KviApplication.h"
#include "KviFileUtils.h"
#include "KviFileDialog.h"
#include "KviControlCodes.h"

#include "ExportOperation.h"

#include <QList>
#include <QFileInfo>
#include <QDir>
#include <QCursor>
#include <QHeaderView>
#include <QPushButton>
#include <QDateEdit>
#include <QLineEdit>
#include <QLabel>
#include <QMouseEvent>
#include <QMessageBox>
#include <QProgressBar>
#include <QProgressDialog>
#include <QTextStream>
#include <QTabWidget>
#include <QCheckBox>
#include <QMenu>
#include <QtConcurrent>

#include <climits> //for INT_MAX

extern LogViewWindow * g_pLogViewWindow;

LogViewListView::LogViewListView(QWidget * pParent)
    : QTreeWidget(pParent)
{
	header()->setSortIndicatorShown(true);
	setColumnCount(1);
	setHeaderLabel(__tr2qs_ctx("Log File", "log"));
	setSelectionMode(QAbstractItemView::SingleSelection);
	setSortingEnabled(true);
	setRootIsDecorated(true);
	setAnimated(true);
}

void LogViewListView::mousePressEvent(QMouseEvent * pEvent)
{
	if(pEvent->button() == Qt::RightButton)
	{
		QTreeWidgetItem * pItem = itemAt(pEvent->pos());
		if(pItem)
			emit rightButtonPressed(pItem, QCursor::pos());
	}
	QTreeWidget::mousePressEvent(pEvent);
}

LogViewWindow::LogViewWindow()
    : KviWindow(KviWindow::LogView, "log")
{
	g_pLogViewWindow = this;
	//m_pLogViewWidget = new KviLogViewWidget(this);

	m_pSplitter = new KviTalSplitter(Qt::Horizontal, this);
	m_pSplitter->setObjectName("main_splitter");
	m_pSplitter->setChildrenCollapsible(false);

	m_pLeftLayout = new KviTalVBox(m_pSplitter);
	m_pTabWidget = new QTabWidget(m_pLeftLayout);
	m_pBottomLayout = new KviTalHBox(m_pLeftLayout);
	m_pProgressBar = new QProgressBar(m_pBottomLayout);

	m_pCancelButton = new QPushButton(m_pBottomLayout);
	m_pCancelButton->setText(__tr2qs_ctx("Cancel", "log"));
	connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(abortFilter()));
	m_pBottomLayout->setVisible(false);

	m_pIndexTab = new KviTalVBox(m_pTabWidget);
	m_pTabWidget->addTab(m_pIndexTab, __tr2qs_ctx("Index", "log"));

	m_pListView = new LogViewListView(m_pIndexTab);

	connect(m_pListView, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(itemSelected(QTreeWidgetItem *, QTreeWidgetItem *)));
	connect(m_pListView, SIGNAL(rightButtonPressed(QTreeWidgetItem *, QPoint)), this, SLOT(rightButtonClicked(QTreeWidgetItem *, QPoint)));

	m_pSearchTab = new QWidget(m_pTabWidget);
	m_pTabWidget->addTab(m_pSearchTab, __tr2qs_ctx("Filter", "log"));

	QGridLayout * pLayout = new QGridLayout(m_pSearchTab);

	m_pShowChannelsCheck = new QCheckBox(__tr2qs_ctx("Show channel logs", "log"), m_pSearchTab);
	m_pShowChannelsCheck->setChecked(true);
	pLayout->addWidget(m_pShowChannelsCheck, 0, 0, 1, 2);

	m_pShowQueryesCheck = new QCheckBox(__tr2qs_ctx("Show query logs", "log"), m_pSearchTab);
	m_pShowQueryesCheck->setChecked(true);
	pLayout->addWidget(m_pShowQueryesCheck, 1, 0, 1, 2);

	m_pShowConsolesCheck = new QCheckBox(__tr2qs_ctx("Show console logs", "log"), m_pSearchTab);
	m_pShowConsolesCheck->setChecked(true);
	pLayout->addWidget(m_pShowConsolesCheck, 2, 0, 1, 2);

	m_pShowDccChatCheck = new QCheckBox(__tr2qs_ctx("Show DCC chat logs", "log"), m_pSearchTab);
	m_pShowDccChatCheck->setChecked(true);
	pLayout->addWidget(m_pShowDccChatCheck, 3, 0, 1, 2);

	m_pShowOtherCheck = new QCheckBox(__tr2qs_ctx("Show other logs", "log"), m_pSearchTab);
	m_pShowOtherCheck->setChecked(true);
	pLayout->addWidget(m_pShowOtherCheck, 4, 0, 1, 2);

	QLabel * pLabel;
	pLabel = new QLabel(__tr2qs_ctx("Contents Filter", "log"), m_pSearchTab);
	pLayout->addWidget(pLabel, 5, 0, 1, 2);

	pLabel = new QLabel(__tr2qs_ctx("Log name mask:", "log"), m_pSearchTab);
	m_pFileNameMask = new QLineEdit(m_pSearchTab);
	pLayout->addWidget(pLabel, 6, 0);
	pLayout->addWidget(m_pFileNameMask, 6, 1);
	connect(m_pFileNameMask, SIGNAL(returnPressed()), this, SLOT(applyFilter()));

	pLabel = new QLabel(__tr2qs_ctx("Log contents mask:", "log"), m_pSearchTab);
	m_pContentsMask = new QLineEdit(m_pSearchTab);
	pLayout->addWidget(pLabel, 7, 0);
	pLayout->addWidget(m_pContentsMask, 7, 1);
	connect(m_pContentsMask, SIGNAL(returnPressed()), this, SLOT(applyFilter()));

	m_pEnableFromFilter = new QCheckBox(__tr2qs_ctx("Only older than:", "log"), m_pSearchTab);
	m_pFromDateEdit = new QDateEdit(m_pSearchTab);
	m_pFromDateEdit->setDate(QDate::currentDate());
	m_pFromDateEdit->setEnabled(false);
	pLayout->addWidget(m_pEnableFromFilter, 8, 0);
	pLayout->addWidget(m_pFromDateEdit, 8, 1);
	connect(m_pEnableFromFilter, SIGNAL(toggled(bool)), m_pFromDateEdit, SLOT(setEnabled(bool)));

	m_pEnableToFilter = new QCheckBox(__tr2qs_ctx("Only newer than:", "log"), m_pSearchTab);
	m_pToDateEdit = new QDateEdit(m_pSearchTab);
	m_pToDateEdit->setDate(QDate::currentDate());
	m_pToDateEdit->setEnabled(false);
	pLayout->addWidget(m_pEnableToFilter, 9, 0);
	pLayout->addWidget(m_pToDateEdit, 9, 1);
	connect(m_pEnableToFilter, SIGNAL(toggled(bool)), m_pToDateEdit, SLOT(setEnabled(bool)));

	m_pFilterButton = new QPushButton(__tr2qs_ctx("Apply Filter", "log"), m_pSearchTab);
	pLayout->addWidget(m_pFilterButton, 10, 1);
	connect(m_pFilterButton, SIGNAL(clicked()), this, SLOT(applyFilter()));

	QWidget * pWidget = new QWidget(m_pSearchTab);
	pWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
	pLayout->addWidget(pWidget, 11, 1);

	m_pIrcView = new KviIrcView(m_pSplitter, this);
	m_pIrcView->setMaxBufferSize(INT_MAX);
	m_pIrcView->setFocusPolicy(Qt::ClickFocus);

	QList<int> li;
	li.append(110);
	li.append(width() - 110);
	m_pSplitter->setSizes(li);

	// Using setData to track the option ordinal that used to be passed as id
	m_pExportLogPopup = new QMenu("exportlog", this);
	QAction * pAction = m_pExportLogPopup->addAction(__tr2qs_ctx("Plain Text File", "log"));
	pAction->setData(LogFile::PlainText);
	pAction = m_pExportLogPopup->addAction(__tr2qs_ctx("HTML Archive", "log"));
	pAction->setData(LogFile::HTML);
	//m_pExportLogPopup->addAction(__tr2qs_ctx("XML file","log"));
	//m_pExportLogPopup->addAction(__tr2qs_ctx("database file","log"));

	// 18.11.14: Originally this hooked activated, but Qt4 kept sending bullshit IDs here
	// https://github.com/kvirc/KVIrc/issues/934#issuecomment-124933890
	connect(m_pExportLogPopup, SIGNAL(triggered(QAction *)), this, SLOT(exportLog(QAction *)));

	m_pTimer = new QTimer(this);
	m_pTimer->setSingleShot(true);
	m_pTimer->setInterval(0);
	connect(m_pTimer, SIGNAL(timeout()), this, SLOT(filterNext()));
	//avoid to execute the long time-consuming procedure of log indexing here:
	//we could still be inside the context of the "Browse log files" QAction
	QTimer::singleShot(0, this, SLOT(cacheFileList()));
}

LogViewWindow::~LogViewWindow()
{
	g_pLogViewWindow = nullptr;
}

void LogViewWindow::keyPressEvent(QKeyEvent * pEvent)
{
	if((pEvent->modifiers() & Qt::ControlModifier) || (pEvent->modifiers() & Qt::AltModifier) || (pEvent->modifiers() & Qt::MetaModifier))
	{
		if(pEvent->key() == Qt::Key_F)
		{
			m_pIrcView->toggleToolWidget();
			return;
		}
	}
	KviWindow::keyPressEvent(pEvent);
}

QPixmap * LogViewWindow::myIconPtr()
{
	return g_pIconManager->getSmallIcon(KviIconManager::Log);
}

void LogViewWindow::resizeEvent(QResizeEvent *)
{
	m_pSplitter->setGeometry(0, 0, width(), height());
}

void LogViewWindow::fillCaptionBuffers()
{
	m_szPlainTextCaption = __tr2qs_ctx("Log Viewer", "log");
}

void LogViewWindow::die()
{
	close();
}

QSize LogViewWindow::sizeHint() const
{
	QSize ret(m_pSplitter->sizeHint().width(), m_pIrcView->sizeHint().height());
	return ret;
}

void LogViewWindow::recurseDirectory(const QString & szDir)
{
	QDir dir(szDir);
	QFileInfoList list = dir.entryInfoList();
	for(int i = 0; i < list.count(); i++)
	{
		QFileInfo info = list[i];
		if(info.isDir())
		{
			// recursive
			if((info.fileName() != "..") && (info.fileName() != "."))
				recurseDirectory(info.filePath());
		}
		else if((info.suffix() == "gz") || (info.suffix() == "log"))
		{
			m_logList.emplace_back(new LogFile(info.filePath()));
		}
	}
}

void LogViewWindow::setupItemList()
{
	if(m_logList.empty())
		return;

	m_pFilterButton->setEnabled(false);
	m_pListView->clear();

	m_bAborted = false;
	m_pBottomLayout->setVisible(true);
	m_pProgressBar->setRange(0, m_logList.size());
	m_pProgressBar->setValue(0);

	m_pLastCategory = nullptr;
	m_pLastGroupItem = nullptr;
	m_currentLog = m_logList.begin();
	m_pTimer->start(); //singleshot
}

void LogViewWindow::applyFilter()
{
	setupItemList();
}

void LogViewWindow::abortFilter()
{
	m_bAborted = true;
}

void LogViewWindow::filterNext()
{
	QString szCurGroup;
	std::shared_ptr<LogFile> pFile = *m_currentLog;

	if(m_currentLog == m_logList.end())
		goto filter_last;

	if(pFile->type() == LogFile::Channel && !m_pShowChannelsCheck->isChecked())
		goto filter_next;
	if(pFile->type() == LogFile::Console && !m_pShowConsolesCheck->isChecked())
		goto filter_next;
	if(pFile->type() == LogFile::DccChat && !m_pShowDccChatCheck->isChecked())
		goto filter_next;
	if(pFile->type() == LogFile::Other && !m_pShowOtherCheck->isChecked())
		goto filter_next;
	if(pFile->type() == LogFile::Query && !m_pShowQueryesCheck->isChecked())
		goto filter_next;

	if(m_pEnableFromFilter->isChecked())
		if(pFile->date() > m_pFromDateEdit->date())
			goto filter_next;

	if(m_pEnableToFilter->isChecked())
		if(pFile->date() < m_pToDateEdit->date())
			goto filter_next;

	if(!m_pFileNameMask->text().isEmpty())
		if(!KviQString::matchString(m_pFileNameMask->text(), pFile->name()))
			goto filter_next;

	if(!m_pContentsMask->text().isEmpty())
	{
		QString szBuffer;
		pFile->getText(szBuffer);
		if(!KviQString::matchString(m_pContentsMask->text(), szBuffer))
			goto filter_next;
	}

	if(m_pLastCategory)
	{
		if(m_pLastCategory->m_eType != pFile->type())
		{
			m_pLastCategory = nullptr;
			for(int i = 0; i < m_pListView->topLevelItemCount(); ++i)
			{
				LogListViewItemType * pTmp = (LogListViewItemType *)m_pListView->topLevelItem(i);
				if(pTmp->m_eType == pFile->type())
				{
					m_pLastCategory = pTmp;
					break;
				}
			}
			if(!m_pLastCategory)
				m_pLastCategory = new LogListViewItemType(m_pListView, pFile->type());
		}
	}
	else
	{
		m_pLastCategory = new LogListViewItemType(m_pListView, pFile->type());
	}

	szCurGroup = __tr2qs_ctx("%1 on %2", "log").arg(pFile->name(), pFile->network());

	if(m_szLastGroup != szCurGroup)
	{
		m_szLastGroup = szCurGroup;
		m_pLastGroupItem = nullptr;
		for(int i = 0; i < m_pLastCategory->childCount(); ++i)
		{
			LogListViewItemFolder * pTmp = (LogListViewItemFolder *)m_pLastCategory->child(i);
			if(pTmp->text(0) == m_szLastGroup)
			{
				m_pLastGroupItem = pTmp;
				break;
			}
		}

		if(!m_pLastGroupItem)
			m_pLastGroupItem = new LogListViewItemFolder(m_pLastCategory, m_szLastGroup);
	}

	new LogListViewLog(m_pLastGroupItem, pFile->type(), pFile);

filter_next:
	++m_currentLog;

filter_last:
	if((m_currentLog != m_logList.end()) && !m_bAborted)
	{
		m_pProgressBar->setValue(m_pProgressBar->value() + 1);
		m_pTimer->start(); //singleshot
	}
	else
	{
		m_pBottomLayout->setVisible(false);
		m_pListView->sortItems(0, Qt::AscendingOrder);
		m_pProgressBar->setValue(0);
		m_pFilterButton->setEnabled(true);

		// Reset m_szLastGroup for next search
		m_szLastGroup = "";
	}
}

void LogViewWindow::cacheFileList()
{
	QString szLogPath;
	g_pApp->getLocalKvircDirectory(szLogPath, KviApplication::Log);
	recurseDirectory(szLogPath);

	setupItemList();
}

void LogViewWindow::itemSelected(QTreeWidgetItem * it, QTreeWidgetItem *)
{
	//A parent node
	m_pIrcView->clearBuffer();
	if(!it || !it->parent() || !(((LogListViewItem *)it)->m_pFileData))
		return;

	QString szText;
	((LogListViewItem *)it)->m_pFileData->getText(szText);

	QStringList lines = szText.split('\n');
	bool bOk;
	int iMsgType;
	for(auto & line : lines)
	{
		QString szNum = line.section(' ', 0, 0);
		iMsgType = szNum.toInt(&bOk);
		if(iMsgType < 0 || iMsgType > (KVI_NUM_MSGTYPE_OPTIONS - 1))
			iMsgType = 0;
		if(bOk)
			outputNoFmt(iMsgType, line.section(' ', 1), KviIrcView::NoRepaint | KviIrcView::NoTimestamp);
		else
			outputNoFmt(0, line, KviIrcView::NoRepaint | KviIrcView::NoTimestamp);
	}
	m_pIrcView->repaint();
}

void LogViewWindow::rightButtonClicked(QTreeWidgetItem * pItem, const QPoint &)
{
	if(!pItem)
		return;
	m_pListView->setCurrentItem(pItem);

	QMenu * pPopup = new QMenu(this);
	if(((LogListViewItem *)pItem)->childCount())
	{
		pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Save)), __tr2qs_ctx("Export All Log Files to", "log"))->setMenu(m_pExportLogPopup);
		pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)), __tr2qs_ctx("Remove All Log Files Within This Folder", "log"), this, SLOT(deleteCurrent()));
	}
	else
	{
		pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Save)), __tr2qs_ctx("Export Log File to", "log"))->setMenu(m_pExportLogPopup);
		pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)), __tr2qs_ctx("Remove Log File", "log"), this, SLOT(deleteCurrent()));
	}

	pPopup->exec(QCursor::pos());
}

void LogViewWindow::deleteCurrent()
{
	LogListViewItem * pItem = dynamic_cast<LogListViewItem *>(m_pListView->currentItem());
	if(!pItem)
		return;

	if(!pItem->childCount())
	{
		if(!pItem->fileName().isNull())
		{
			if(QMessageBox::question(
			       this,
			       __tr2qs_ctx("Confirm Current User Log Deletion", "log"),
			       __tr2qs_ctx("Do you really wish to delete this log?", "log"))
			    != QMessageBox::Yes)
				return;

			KviFileUtils::removeFile(pItem->fileName());
			if(!pItem->parent()->childCount())
				delete pItem->parent();

			delete pItem;
			m_pIrcView->clearBuffer();
		}
		return;
	}

	if(QMessageBox::question(
	       this,
	       __tr2qs_ctx("Confirm Current User Logs Deletion", "log"),
	       __tr2qs_ctx("Do you really wish to delete all these logs?", "log"))
	    != QMessageBox::Yes)
		return;
	KviPointerList<LogListViewItem> itemsList;
	itemsList.setAutoDelete(false);
	for(int i = 0; i < pItem->childCount(); i++)
	{
		if(!pItem->child(i)->childCount())
		{
			itemsList.append((LogListViewItem *)pItem->child(i));
			continue;
		}
		LogListViewItem * pChild = (LogListViewItem *)pItem->child(i);
		for(int j = 0; j < pChild->childCount(); j++)
		{
			if(!(LogListViewItem *)pChild->child(j))
			{
				qDebug("Null pointer in logviewitem");
				continue;
			}
			itemsList.append((LogListViewItem *)pChild->child(j));
		}
	}
	for(unsigned int u = 0; u < itemsList.count(); u++)
	{
		LogListViewItem * pCurItem = itemsList.at(u);
		if(!pCurItem->fileName().isNull())
			KviFileUtils::removeFile(pCurItem->fileName());
	}
	delete pItem;
}

void LogViewWindow::exportLog(QAction * pAction)
{
	/* This slot is required as Qt4 has started to screw up the menu ordinal
	 * sent with the old activated signal - the ordinal is now stored as
	 * QAction user data */
	if(pAction)
		exportLog(static_cast<LogFile::ExportType>(pAction->data().toInt()));
	else
		qDebug("LogViewWindow::exportLog called with invalid pAction");
}

void LogViewWindow::exportLog(LogFile::ExportType exportType)
{
	LogListViewItem * pItem = (LogListViewItem *)(m_pListView->currentItem());
	if(!pItem)
		return;

	if(!pItem->childCount())
	{
		std::shared_ptr<LogFile> pLog { pItem->log() };

		QString szDate = pLog->date().toString("yyyy.MM.dd");

		QString szLog = KVI_OPTION_STRING(KviOption_stringLogsExportPath).trimmed();
		if(!szLog.isEmpty())
			szLog += KVI_PATH_SEPARATOR_CHAR;
		szLog += QString("%1_%2.%3_%4").arg(pLog->typeString(), pLog->name(), pLog->network(), szDate);
		KviFileUtils::adjustFilePath(szLog);

		// Getting output file path from the user, with overwrite confirmation
		if(!KviFileDialog::askForSaveFileName(
		       szLog,
		       __tr2qs_ctx("Export Log - KVIrc", "log"),
		       szLog,
		       QString(),
		       false,
		       true,
		       true,
		       this))
			return;

		// Export the log
		pLog->createLog(exportType, szLog);
		return;
	}

	// We selected a node in the log list, scan the children
	std::vector<std::shared_ptr<LogFile>> logList;
	for(int i = 0; i < pItem->childCount(); i++)
	{
		if(!pItem->child(i)->childCount())
		{
			// The child is a log file, append it to the list
			LogListViewItem * pViewItem = static_cast<LogListViewItem *>(pItem->child(i));
			std::shared_ptr<LogFile> pLog { pViewItem->log() };
			logList.push_back(pLog);
			continue;
		}

		// The child is a node, scan it
		LogListViewItem * pChild = (LogListViewItem *)pItem->child(i);
		for(int j = 0; j < pChild->childCount(); j++)
		{
			if(!(LogListViewItem *)pChild->child(j))
			{
				qDebug("Null pointer in logviewitem");
				continue;
			}

			// Add the child to the list
			LogListViewItem * pViewItem = static_cast<LogListViewItem *>(pItem->child(j));
			std::shared_ptr<LogFile> pLog { pViewItem->log() };
			logList.push_back(pLog);
		}
	}

	// Select output directory
	QString szDir = KVI_OPTION_STRING(KviOption_stringLogsExportPath).trimmed();
	if(!KviFileDialog::askForDirectoryName(
	       szDir,
	       __tr2qs_ctx("Export Log - KVIrc", "log"),
	       szDir,
	       QString(),
	       false,
	       true,
	       this))
		return;
	KVI_OPTION_STRING(KviOption_stringLogsExportPath) = szDir;

	// Begin asynchronously writing the logs to persistent storage
	ExportOperation * worker = new ExportOperation(logList, exportType, szDir);
	worker->start();
}