diff options
Diffstat (limited to 'src/modules/logview')
| -rw-r--r-- | src/modules/logview/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/modules/logview/ExportOperation.cpp | 45 | ||||
| -rw-r--r-- | src/modules/logview/ExportOperation.h | 27 | ||||
| -rw-r--r-- | src/modules/logview/LogFile.cpp | 249 | ||||
| -rw-r--r-- | src/modules/logview/LogFile.h | 11 | ||||
| -rw-r--r-- | src/modules/logview/LogViewWidget.cpp | 14 | ||||
| -rw-r--r-- | src/modules/logview/LogViewWidget.h | 21 | ||||
| -rw-r--r-- | src/modules/logview/LogViewWindow.cpp | 330 | ||||
| -rw-r--r-- | src/modules/logview/LogViewWindow.h | 19 | ||||
| -rw-r--r-- | src/modules/logview/libkvilogview.cpp | 33 |
10 files changed, 441 insertions, 309 deletions
diff --git a/src/modules/logview/CMakeLists.txt b/src/modules/logview/CMakeLists.txt index e7790b13a..4fb6601bb 100644 --- a/src/modules/logview/CMakeLists.txt +++ b/src/modules/logview/CMakeLists.txt @@ -5,6 +5,7 @@ set(kvilogview_SRCS LogFile.cpp LogViewWidget.cpp LogViewWindow.cpp + ExportOperation.cpp ) set(kvi_module_name kvilogview) diff --git a/src/modules/logview/ExportOperation.cpp b/src/modules/logview/ExportOperation.cpp new file mode 100644 index 000000000..49c9f47be --- /dev/null +++ b/src/modules/logview/ExportOperation.cpp @@ -0,0 +1,45 @@ +#include "ExportOperation.h" + +#include <QProgressDialog> +#include <QFutureWatcher> +#include <QVector> +#include <QtConcurrent> + +#include "LogFile.h" +#include "LogViewWindow.h" +#include "KviFileUtils.h" + +ExportOperation::ExportOperation(const std::vector<std::shared_ptr<LogFile>> & logs, LogFile::ExportType type, QString szDir, QObject * parent) + : QObject(parent) + , m_logs(logs) + , m_type(type) + , m_szDir(szDir) +{ +} + +void ExportOperation::start() +{ + QProgressDialog * pProgressDialog = new QProgressDialog("Exporting logs...", "Cancel", 0, m_logs.size()); + QFutureWatcher<void> * pFutureWatcher = new QFutureWatcher<void>(); + + QObject::connect(pFutureWatcher, &QFutureWatcher<void>::finished, pProgressDialog, &QProgressDialog::deleteLater); + QObject::connect(pFutureWatcher, &QFutureWatcher<void>::finished, pFutureWatcher, &QFutureWatcher<void>::deleteLater); + QObject::connect(pFutureWatcher, &QFutureWatcher<void>::finished, this, &ExportOperation::deleteLater); + + QObject::connect(pProgressDialog, &QProgressDialog::canceled, pFutureWatcher, &QFutureWatcher<void>::cancel); + QObject::connect(pFutureWatcher, &QFutureWatcher<void>::progressValueChanged, pProgressDialog, &QProgressDialog::setValue); + + // The directory string and the export type could be captured by value if + // this function was inlined. However, because the QtConcurrent functions + // aside from QtConcurrent::run only operate on references the list of + // pointers might expire, hence the purpose of this class. + pFutureWatcher->setFuture(QtConcurrent::map(m_logs, [this](const std::shared_ptr<LogFile> & pLog) { + const QString szDate = pLog->date().toString("yyyy.MM.dd"); + QString filename = QString("%1_%2.%3_%4").arg(pLog->typeString(), pLog->name(), pLog->network(), szDate); + filename.replace(QRegExp("[\\\\/:*?\"<>|]"), "_"); + QString szLog = m_szDir + KVI_PATH_SEPARATOR_CHAR + filename; + KviFileUtils::adjustFilePath(szLog); + pLog->createLog(m_type, szLog); + })); + pProgressDialog->show(); +} diff --git a/src/modules/logview/ExportOperation.h b/src/modules/logview/ExportOperation.h new file mode 100644 index 000000000..bb531b5dd --- /dev/null +++ b/src/modules/logview/ExportOperation.h @@ -0,0 +1,27 @@ +#ifndef _EXPORTOPERATION_H_ +#define _EXPORTOPERATION_H_ + +#include "LogFile.h" + +#include <QObject> + +#include <vector> +#include <memory> + +// ExportOperation is a small container class for the data necessary +// to export log files. The purpose of ExportOperation is to ensure the +// lifetime of that data--in particular, the list of log files. +class ExportOperation : public QObject +{ + Q_OBJECT + + std::vector<std::shared_ptr<LogFile>> m_logs; + const QString m_szDir; + const LogFile::ExportType m_type; + +public: + ExportOperation(const std::vector<std::shared_ptr<LogFile>> & logs, LogFile::ExportType type, QString szDir, QObject * parent = nullptr); + void start(); +}; + +#endif diff --git a/src/modules/logview/LogFile.cpp b/src/modules/logview/LogFile.cpp index be244077b..0c15b27be 100644 --- a/src/modules/logview/LogFile.cpp +++ b/src/modules/logview/LogFile.cpp @@ -24,12 +24,20 @@ #include "LogFile.h" +#include "kvi_settings.h" +#include "KviApplication.h" +#include "KviControlCodes.h" #include "KviQString.h" #include "KviCString.h" +#include "KviHtmlGenerator.h" +#include "KviIconManager.h" +#include "KviLocale.h" #include "KviOptions.h" #include "KviFileUtils.h" #include <QFileInfo> +#include <QDir> +#include <QTextStream> #include <QLocale> #ifdef COMPILE_ZLIB_SUPPORT @@ -144,7 +152,7 @@ LogFile::LogFile(const QString & szName) } } -void LogFile::getText(QString & szText) +void LogFile::getText(QString & szText) const { QString szLogName = fileName(); QFile logFile; @@ -189,3 +197,242 @@ void LogFile::getText(QString & szText) } #endif } + +void LogFile::createLog(ExportType exportType, QString szLog, QString * pszFile) const +{ + QRegExp rx; + QString szLogDir, szInputBuffer, szOutputBuffer, szLine, szTmp; + QString szDate = date().toString("yyyy.MM.dd"); + + /* Save export directory - this directory path is also used in the HTML export + * and info is used when working with pszFile */ + QFileInfo info(szLog); + szLogDir = info.absoluteDir().absolutePath() + KVI_PATH_SEPARATOR_CHAR; + + /* Reading in log file - LogFiles are read in as bytes, so '\r' isn't + * sanitised by default */ + getText(szInputBuffer); + QStringList lines = szInputBuffer.replace('\r', "").split('\n'); + + switch(exportType) + { + case LogFile::PlainText: + { + /* Only append extension if it isn't there already (e.g. a specific + * file is to be overwritten) */ + if(!szLog.endsWith(".txt")) + szLog += ".txt"; + + // Scan the file + for(auto & line : lines) + { + szTmp = line; + szLine = KviControlCodes::stripControlBytes(szTmp); + + // Remove icons' code + rx.setPattern("^\\d{1,3}\\s"); + szLine.replace(rx, ""); + + // Remove link from a user speaking, deal with (and keep) various ranks + // e.g.: <!ncHelLViS69> --> <HelLViS69> + rx.setPattern("\\s<([+%@&~!]?)!nc"); + szLine.replace(rx, " <\\1"); + + // Remove link from a nick in a mask + // e.g.: !nFoo [~bar@!hfoo.bar] --> Foo [~bar@!hfoo.bar] + rx.setPattern("\\s!n"); + szLine.replace(rx, " "); + + // Remove link from a host in a mask + // e.g.: Foo [~bar@!hfoo.bar] --> Foo [~bar@foo.bar] + rx.setPattern("@!h"); + szLine.replace(rx, "@"); + + // Remove link from a channel + // e.g.: !c#KVIrc --> #KVIrc + rx.setPattern("!c#"); + szLine.replace(rx, "#"); + + szOutputBuffer += szLine; + szOutputBuffer += "\n"; + } + + break; + } + case LogFile::HTML: + { + /* Only append extension if it isn't there already (e.g. a specific + * file is to be overwritten) */ + if(!szLog.endsWith(".html")) + szLog += ".html"; + + szTmp = QString("KVIrc %1 %2").arg(KVI_VERSION).arg(KVI_RELEASE_NAME); + QString szNick = ""; + bool bFirstLine = true; + + QString szTitle; + switch(type()) + { + case LogFile::Channel: + szTitle = __tr2qs_ctx("Channel %1 on %2", "log").arg(name(), network()); + break; + case LogFile::Console: + szTitle = __tr2qs_ctx("Console on %1", "log").arg(network()); + break; + case LogFile::Query: + szTitle = __tr2qs_ctx("Query with: %1 on %2", "log").arg(name(), network()); + break; + case LogFile::DccChat: + szTitle = __tr2qs_ctx("DCC Chat with: %1", "log").arg(name()); + break; + case LogFile::Other: + szTitle = __tr2qs_ctx("Something on: %1", "log").arg(network()); + break; + } + + // Prepare HTML document + szOutputBuffer += "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"; + szOutputBuffer += "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"; + szOutputBuffer += "<head>\n"; + szOutputBuffer += "\t<meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=utf-8\" />\n"; + szOutputBuffer += "\t<meta name=\"author\" content=\"" + szTmp + "\" />\n"; + szOutputBuffer += "\t<title>" + szTitle + "</title>\n"; + szOutputBuffer += "</head>\n<body>\n"; + szOutputBuffer += "<h2>" + szTitle + "</h2>\n<h3>Date: " + szDate + "</h3>\n"; + + // Scan the file + for(auto & line : lines) + { + szTmp = line; + + // Find who has talked + QString szTmpNick = szTmp.section(" ", 2, 2); + if((szTmpNick.left(1) != "<") && (szTmpNick.right(1) != ">")) + szTmpNick = ""; + + // locate msgtype + QString szNum = szTmp.section(' ', 0, 0); + bool bOk; + int iMsgType = szNum.toInt(&bOk); + + // only human text for now... + if(iMsgType != 24 && iMsgType != 25 && iMsgType != 26) + continue; + + // remove msgtype tag + szTmp = szTmp.remove(0, szNum.length() + 1); + + szTmp = KviHtmlGenerator::convertToHtml(szTmp, true); + + // insert msgtype icon at start of the current text line + KviMessageTypeSettings msg(KVI_OPTION_MSGTYPE(iMsgType)); + QString szIcon = g_pIconManager->getSmallIconResourceName((KviIconManager::SmallIcon)msg.pixId()); + szTmp.prepend("<img src=\"" + szIcon + R"(" alt="" /> )"); + + /* + * Check if the nick who has talked is the same of the above line. + * If so, we have to put the line as it is, otherwise we have to + * open a new paragraph + */ + if(szTmpNick != szNick) + { + /* + * People is not the same, close the paragraph opened + * before and open a new one + */ + if(!bFirstLine) + szOutputBuffer += "</p>\n"; + szTmp.prepend("<p>"); + + szNick = szTmpNick; + } + else + { + // Break the line + szTmp.prepend("<br />\n"); + } + + // remove internal tags before nickname + rx.setPattern(">([+%@&~!]?)!nc"); + szTmp.replace(rx, ">\\1"); + szTmp.replace(">&!nc", ">&"); + + szOutputBuffer += szTmp; + bFirstLine = false; + } + + // Close the last paragraph + szOutputBuffer += "</p>\n"; + + // regexp to search all embedded icons + rx.setPattern("<img src=\"smallicons:([^\"]+)"); + int iIndex = szOutputBuffer.indexOf(rx); + QStringList szImagesList; + + // search for icons + while(iIndex >= 0) + { + int iLength = rx.matchedLength(); + QString szCap = rx.cap(1); + + // if the icon isn't in the images list then add + if(szImagesList.indexOf(szCap) == -1) + szImagesList.append(szCap); + iIndex = szOutputBuffer.indexOf(rx, iIndex + iLength); + } + + // get current theme path + QString szCurrentThemePath; + g_pApp->getLocalKvircDirectory(szCurrentThemePath, KviApplication::Themes, KVI_OPTION_STRING(KviOption_stringIconThemeSubdir)); + szCurrentThemePath += KVI_PATH_SEPARATOR_CHAR; + + // current coresmall path + szCurrentThemePath += "coresmall"; + szCurrentThemePath += KVI_PATH_SEPARATOR_CHAR; + + // check if coresmall exists in current theme + if(!KviFileUtils::directoryExists(szCurrentThemePath)) + { + // get global coresmall path + g_pApp->getGlobalKvircDirectory(szCurrentThemePath, KviApplication::Pics, "coresmall"); + KviQString::ensureLastCharIs(szCurrentThemePath, QChar(KVI_PATH_SEPARATOR_CHAR)); + } + + // copy all icons to the log destination folder + for(int i = 0; i < szImagesList.count(); i++) + { + QString szSourceFile = szCurrentThemePath + szImagesList.at(i); + QString szDestFile = szLogDir + szImagesList.at(i); + KviFileUtils::copyFile(szSourceFile, szDestFile); + } + + // remove internal tags + rx.setPattern("<qt>|</qt>|smallicons:"); + szOutputBuffer.replace(rx, ""); + + // Close the document + szOutputBuffer += "</body>\n</html>\n"; + + break; + } + } + + // File overwriting already dealt with when file path was obtained + QFile log(szLog); + if(!log.open(QIODevice::WriteOnly | QIODevice::Text)) + return; + + if(pszFile) + { + *pszFile = ""; + *pszFile = info.filePath(); + } + + // Ensure we're writing in UTF-8 + QTextStream output(&log); + output.setCodec("UTF-8"); + output << szOutputBuffer; + + // Close file descriptors + log.close(); +} diff --git a/src/modules/logview/LogFile.h b/src/modules/logview/LogFile.h index 9b4984aa7..eb0b4ce64 100644 --- a/src/modules/logview/LogFile.h +++ b/src/modules/logview/LogFile.h @@ -143,7 +143,16 @@ public: * \param szText The buffer where to save the contents of the log * \return void */ - void getText(QString & szText); + void getText(QString & szText) const; + + /** + * \brief Exports the log and creates the file in the selected format + * \param exportType The type of file to export the log as. Either PlainText or HTML. + * \param szLog The absolute path of the file to be created + * \param pszFile The buffer to store the exported log name + * \return void + */ + void createLog(ExportType exportType, QString szLog, QString * pszFile = nullptr) const; }; #endif // _LOGFILE_H_ diff --git a/src/modules/logview/LogViewWidget.cpp b/src/modules/logview/LogViewWidget.cpp index cf90ffa74..ed190a324 100644 --- a/src/modules/logview/LogViewWidget.cpp +++ b/src/modules/logview/LogViewWidget.cpp @@ -38,14 +38,18 @@ #include <zlib.h> #endif -LogListViewItem::LogListViewItem(QTreeWidgetItem * pPar, LogFile::Type eType, LogFile * pLog) - : QTreeWidgetItem(pPar), m_eType(eType), m_pFileData(pLog) +LogListViewItem::LogListViewItem(QTreeWidgetItem * pPar, LogFile::Type eType, std::shared_ptr<LogFile> pLog) + : QTreeWidgetItem(pPar) + , m_eType(eType) + , m_pFileData(pLog) { setText(0, m_pFileData ? m_pFileData->name() : QString()); } -LogListViewItem::LogListViewItem(QTreeWidget * pPar, LogFile::Type eType, LogFile * pLog) - : QTreeWidgetItem(pPar), m_eType(eType), m_pFileData(pLog) +LogListViewItem::LogListViewItem(QTreeWidget * pPar, LogFile::Type eType, std::shared_ptr<LogFile> pLog) + : QTreeWidgetItem(pPar) + , m_eType(eType) + , m_pFileData(pLog) { setText(0, m_pFileData ? m_pFileData->name() : QString()); } @@ -89,7 +93,7 @@ LogListViewItemType::LogListViewItemType(QTreeWidget * pPar, LogFile::Type eType setText(0, szText); } -LogListViewLog::LogListViewLog(QTreeWidgetItem * pPar, LogFile::Type eType, LogFile * pLog) +LogListViewLog::LogListViewLog(QTreeWidgetItem * pPar, LogFile::Type eType, std::shared_ptr<LogFile> pLog) : LogListViewItem(pPar, eType, pLog) { setText(0, m_pFileData->date().toString("yyyy-MM-dd")); diff --git a/src/modules/logview/LogViewWidget.h b/src/modules/logview/LogViewWidget.h index 9092dae84..203d05d90 100644 --- a/src/modules/logview/LogViewWidget.h +++ b/src/modules/logview/LogViewWidget.h @@ -31,19 +31,21 @@ #include <QTreeWidget> +#include <memory> + class LogListViewItem : public QTreeWidgetItem { public: - LogListViewItem(QTreeWidgetItem * pPar, LogFile::Type eType, LogFile * pLog); - LogListViewItem(QTreeWidget * pPar, LogFile::Type eType, LogFile * pLog); - ~LogListViewItem(){}; + LogListViewItem(QTreeWidgetItem * pPar, LogFile::Type eType, std::shared_ptr<LogFile> pLog); + LogListViewItem(QTreeWidget * pPar, LogFile::Type eType, std::shared_ptr<LogFile> pLog); + ~LogListViewItem() {}; public: LogFile::Type m_eType; - LogFile * m_pFileData; + std::shared_ptr<LogFile> m_pFileData; public: - LogFile * log() { return m_pFileData; }; + std::weak_ptr<LogFile> log() { return m_pFileData; }; virtual QString fileName() const { return QString(); }; }; @@ -51,7 +53,7 @@ class LogListViewItemFolder : public LogListViewItem { public: LogListViewItemFolder(QTreeWidgetItem * pPar, const QString & szLabel); - ~LogListViewItemFolder(){}; + ~LogListViewItemFolder() {}; public: }; @@ -60,15 +62,16 @@ class LogListViewItemType : public LogListViewItem { public: LogListViewItemType(QTreeWidget * pPar, LogFile::Type eType); - ~LogListViewItemType(){}; + ~LogListViewItemType() {}; }; class LogListViewLog : public LogListViewItem { public: - LogListViewLog(QTreeWidgetItem * pPar, LogFile::Type eType, LogFile * pLog); - ~LogListViewLog(){}; + LogListViewLog(QTreeWidgetItem * pPar, LogFile::Type eType, std::shared_ptr<LogFile> pLog); + ~LogListViewLog() {}; virtual QString fileName() const { return m_pFileData->fileName(); }; + protected: bool operator<(const QTreeWidgetItem & other) const { diff --git a/src/modules/logview/LogViewWindow.cpp b/src/modules/logview/LogViewWindow.cpp index 902339cb4..754e6bef5 100644 --- a/src/modules/logview/LogViewWindow.cpp +++ b/src/modules/logview/LogViewWindow.cpp @@ -41,6 +41,8 @@ #include "KviFileDialog.h" #include "KviControlCodes.h" +#include "ExportOperation.h" + #include <QList> #include <QFileInfo> #include <QDir> @@ -53,10 +55,12 @@ #include <QMouseEvent> #include <QMessageBox> #include <QProgressBar> +#include <QProgressDialog> #include <QTextStream> #include <QTabWidget> #include <QCheckBox> #include <QMenu> +#include <QtConcurrent> #include <climits> //for INT_MAX @@ -268,14 +272,14 @@ void LogViewWindow::recurseDirectory(const QString & szDir) } else if((info.suffix() == "gz") || (info.suffix() == "log")) { - m_logList.append(new LogFile(info.filePath())); + m_logList.emplace_back(new LogFile(info.filePath())); } } } void LogViewWindow::setupItemList() { - if(m_logList.isEmpty()) + if(m_logList.empty()) return; m_pFilterButton->setEnabled(false); @@ -283,12 +287,12 @@ void LogViewWindow::setupItemList() m_bAborted = false; m_pBottomLayout->setVisible(true); - m_pProgressBar->setRange(0, m_logList.count()); + m_pProgressBar->setRange(0, m_logList.size()); m_pProgressBar->setValue(0); m_pLastCategory = nullptr; m_pLastGroupItem = nullptr; - m_logList.first(); + m_currentLog = m_logList.begin(); m_pTimer->start(); //singleshot } @@ -305,8 +309,9 @@ void LogViewWindow::abortFilter() void LogViewWindow::filterNext() { QString szCurGroup; - LogFile * pFile = m_logList.current(); - if(!pFile) + std::shared_ptr<LogFile> pFile = *m_currentLog; + + if(m_currentLog == m_logList.end()) goto filter_last; if(pFile->type() == LogFile::Channel && !m_pShowChannelsCheck->isChecked()) @@ -386,10 +391,10 @@ void LogViewWindow::filterNext() new LogListViewLog(m_pLastGroupItem, pFile->type(), pFile); filter_next: - pFile = m_logList.next(); + ++m_currentLog; filter_last: - if(pFile && !m_bAborted) + if((m_currentLog != m_logList.end()) && !m_bAborted) { m_pProgressBar->setValue(m_pProgressBar->value() + 1); m_pTimer->start(); //singleshot @@ -451,7 +456,6 @@ void LogViewWindow::rightButtonClicked(QTreeWidgetItem * pItem, const QPoint &) QMenu * pPopup = new QMenu(this); if(((LogListViewItem *)pItem)->childCount()) { - // TODO: probably should allow to specify a directory instead of asking for file path on each log file 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())); } @@ -534,12 +538,12 @@ void LogViewWindow::exportLog(QAction * pAction) * sent with the old activated signal - the ordinal is now stored as * QAction user data */ if(pAction) - exportLog(pAction->data().toInt()); + exportLog(static_cast<LogFile::ExportType>(pAction->data().toInt())); else qDebug("LogViewWindow::exportLog called with invalid pAction"); } -void LogViewWindow::exportLog(int iId) +void LogViewWindow::exportLog(LogFile::ExportType exportType) { LogListViewItem * pItem = (LogListViewItem *)(m_pListView->currentItem()); if(!pItem) @@ -547,20 +551,43 @@ void LogViewWindow::exportLog(int iId) 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 - createLog(pItem->log(), iId); + pLog->createLog(exportType, szLog); return; } // We selected a node in the log list, scan the children - KviPointerList<LogListViewItem> logList; - logList.setAutoDelete(false); + 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 - logList.append((LogListViewItem *)pItem->child(i)); + LogListViewItem * pViewItem = static_cast<LogListViewItem *>(pItem->child(i)); + std::shared_ptr<LogFile> pLog { pViewItem->log() }; + logList.push_back(pLog); continue; } @@ -575,275 +602,26 @@ void LogViewWindow::exportLog(int iId) } // Add the child to the list - logList.append((LogListViewItem *)pChild->child(j)); + LogListViewItem * pViewItem = static_cast<LogListViewItem *>(pItem->child(j)); + std::shared_ptr<LogFile> pLog { pViewItem->log() }; + logList.push_back(pLog); } } - // Scan the list - for(unsigned int u = 0; u < logList.count(); u++) - { - LogListViewItem * pCurItem = logList.at(u); - createLog(pCurItem->log(), iId); - } -} - -void LogViewWindow::createLog(LogFile * pLog, int iId, QString * pszFile) -{ - if(!pLog) - return; - - QRegExp rx; - QString szLog, szLogDir, szInputBuffer, szOutputBuffer, szLine, szTmp; - QString szDate = pLog->date().toString("yyyy.MM.dd"); - - /* Fetching previous export path and concatenating with generated filename - * adjustFilePath is for file paths not directory paths */ - 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, + // Select output directory + QString szDir = KVI_OPTION_STRING(KviOption_stringLogsExportPath).trimmed(); + if(!KviFileDialog::askForDirectoryName( + szDir, __tr2qs_ctx("Export Log - KVIrc", "log"), - szLog, + szDir, QString(), false, true, - true, this)) return; + KVI_OPTION_STRING(KviOption_stringLogsExportPath) = szDir; - /* Save export directory - this directory path is also used in the HTML export - * and info is used when working with pszFile */ - QFileInfo info(szLog); - szLogDir = info.absoluteDir().absolutePath(); - KVI_OPTION_STRING(KviOption_stringLogsExportPath) = szLogDir; - - /* Reading in log file - LogFiles are read in as bytes, so '\r' isn't - * sanitised by default */ - pLog->getText(szInputBuffer); - QStringList lines = szInputBuffer.replace('\r', "").split('\n'); - - switch(iId) - { - case LogFile::PlainText: - { - /* Only append extension if it isn't there already (e.g. a specific - * file is to be overwritten) */ - if(!szLog.endsWith(".txt")) - szLog += ".txt"; - - // Scan the file - for(auto & line : lines) - { - szTmp = line; - szLine = KviControlCodes::stripControlBytes(szTmp); - - // Remove icons' code - rx.setPattern("^\\d{1,3}\\s"); - szLine.replace(rx, ""); - - // Remove link from a user speaking, deal with (and keep) various ranks - // e.g.: <!ncHelLViS69> --> <HelLViS69> - rx.setPattern("\\s<([+%@&~!]?)!nc"); - szLine.replace(rx, " <\\1"); - - // Remove link from a nick in a mask - // e.g.: !nFoo [~bar@!hfoo.bar] --> Foo [~bar@!hfoo.bar] - rx.setPattern("\\s!n"); - szLine.replace(rx, " "); - - // Remove link from a host in a mask - // e.g.: Foo [~bar@!hfoo.bar] --> Foo [~bar@foo.bar] - rx.setPattern("@!h"); - szLine.replace(rx, "@"); - - // Remove link from a channel - // e.g.: !c#KVIrc --> #KVIrc - rx.setPattern("!c#"); - szLine.replace(rx, "#"); - - szOutputBuffer += szLine; - szOutputBuffer += "\n"; - } - - break; - } - case LogFile::HTML: - { - /* Only append extension if it isn't there already (e.g. a specific - * file is to be overwritten) */ - if(!szLog.endsWith(".html")) - szLog += ".html"; - - szTmp = QString("KVIrc %1 %2").arg(KVI_VERSION).arg(KVI_RELEASE_NAME); - QString szNick = ""; - bool bFirstLine = true; - - QString szTitle; - switch(pLog->type()) - { - case LogFile::Channel: - szTitle = __tr2qs_ctx("Channel %1 on %2", "log").arg(pLog->name(), pLog->network()); - break; - case LogFile::Console: - szTitle = __tr2qs_ctx("Console on %1", "log").arg(pLog->network()); - break; - case LogFile::Query: - szTitle = __tr2qs_ctx("Query with: %1 on %2", "log").arg(pLog->name(), pLog->network()); - break; - case LogFile::DccChat: - szTitle = __tr2qs_ctx("DCC Chat with: %1", "log").arg(pLog->name()); - break; - case LogFile::Other: - szTitle = __tr2qs_ctx("Something on: %1", "log").arg(pLog->network()); - break; - } - - // Prepare HTML document - szOutputBuffer += "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"; - szOutputBuffer += "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n"; - szOutputBuffer += "<head>\n"; - szOutputBuffer += "\t<meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=utf-8\" />\n"; - szOutputBuffer += "\t<meta name=\"author\" content=\"" + szTmp + "\" />\n"; - szOutputBuffer += "\t<title>" + szTitle + "</title>\n"; - szOutputBuffer += "</head>\n<body>\n"; - szOutputBuffer += "<h2>" + szTitle + "</h2>\n<h3>Date: " + szDate + "</h3>\n"; - - // Scan the file - for(auto & line : lines) - { - szTmp = line; - - // Find who has talked - QString szTmpNick = szTmp.section(" ", 2, 2); - if((szTmpNick.left(1) != "<") && (szTmpNick.right(1) != ">")) - szTmpNick = ""; - - // locate msgtype - QString szNum = szTmp.section(' ', 0, 0); - bool bOk; - int iMsgType = szNum.toInt(&bOk); - - // only human text for now... - if(iMsgType != 24 && iMsgType != 25 && iMsgType != 26) - continue; - - // remove msgtype tag - szTmp = szTmp.remove(0, szNum.length() + 1); - - szTmp = KviHtmlGenerator::convertToHtml(szTmp, true); - - // insert msgtype icon at start of the current text line - KviMessageTypeSettings msg(KVI_OPTION_MSGTYPE(iMsgType)); - QString szIcon = g_pIconManager->getSmallIconResourceName((KviIconManager::SmallIcon)msg.pixId()); - szTmp.prepend("<img src=\"" + szIcon + R"(" alt="" /> )"); - - /* - * Check if the nick who has talked is the same of the above line. - * If so, we have to put the line as it is, otherwise we have to - * open a new paragraph - */ - if(szTmpNick != szNick) - { - /* - * People is not the same, close the paragraph opened - * before and open a new one - */ - if(!bFirstLine) - szOutputBuffer += "</p>\n"; - szTmp.prepend("<p>"); - - szNick = szTmpNick; - } - else - { - // Break the line - szTmp.prepend("<br />\n"); - } - - szOutputBuffer += szTmp; - bFirstLine = false; - } - - // Close the last paragraph - szOutputBuffer += "</p>\n"; - - // regexp to search all embedded icons - rx.setPattern("<img src=\"smallicons:([^\"]+)"); - int iIndex = szOutputBuffer.indexOf(rx); - QStringList szImagesList; - - // search for icons - while(iIndex >= 0) - { - int iLength = rx.matchedLength(); - QString szCap = rx.cap(1); - - // if the icon isn't in the images list then add - if(szImagesList.indexOf(szCap) == -1) - szImagesList.append(szCap); - iIndex = szOutputBuffer.indexOf(rx, iIndex + iLength); - } - - // get current theme path - QString szCurrentThemePath; - g_pApp->getLocalKvircDirectory(szCurrentThemePath, KviApplication::Themes, KVI_OPTION_STRING(KviOption_stringIconThemeSubdir)); - szCurrentThemePath += KVI_PATH_SEPARATOR_CHAR; - - // current coresmall path - szCurrentThemePath += "coresmall"; - szCurrentThemePath += KVI_PATH_SEPARATOR_CHAR; - - // check if coresmall exists in current theme - if(!KviFileUtils::directoryExists(szCurrentThemePath)) - { - // get global coresmall path - g_pApp->getGlobalKvircDirectory(szCurrentThemePath, KviApplication::Pics, "coresmall"); - KviQString::ensureLastCharIs(szCurrentThemePath, QChar(KVI_PATH_SEPARATOR_CHAR)); - } - - // copy all icons to the log destination folder - for(int i = 0; i < szImagesList.count(); i++) - { - QString szSourceFile = szCurrentThemePath + szImagesList.at(i); - QString szDestFile = szLogDir + szImagesList.at(i); - KviFileUtils::copyFile(szSourceFile, szDestFile); - } - - // remove internal tags - rx.setPattern("<qt>|</qt>|smallicons:"); - szOutputBuffer.replace(rx, ""); - szOutputBuffer.replace(">!nc", ">"); - szOutputBuffer.replace("@!nc", "@"); - szOutputBuffer.replace("%!nc", "%"); - - // Close the document - szOutputBuffer += "</body>\n</html>\n"; - - break; - } - } - - // File overwriting already dealt with when file path was obtained - QFile log(szLog); - if(!log.open(QIODevice::WriteOnly | QIODevice::Text)) - return; - - if(pszFile) - { - *pszFile = ""; - *pszFile = info.filePath(); - } - - // Ensure we're writing in UTF-8 - QTextStream output(&log); - output.setCodec("UTF-8"); - output << szOutputBuffer; - - // Close file descriptors - log.close(); + // Begin asynchronously writing the logs to persistent storage + ExportOperation * worker = new ExportOperation(logList, exportType, szDir); + worker->start(); } diff --git a/src/modules/logview/LogViewWindow.h b/src/modules/logview/LogViewWindow.h index b90866b39..fb479a726 100644 --- a/src/modules/logview/LogViewWindow.h +++ b/src/modules/logview/LogViewWindow.h @@ -36,6 +36,8 @@ #include "KviPointerList.h" #include <QTreeWidget> +#include <vector> +#include <memory> class KviLogViewWidget; class LogListViewItem; @@ -52,7 +54,7 @@ class LogViewListView : public QTreeWidget Q_OBJECT public: LogViewListView(QWidget *); - ~LogViewListView(){}; + ~LogViewListView() {}; protected: void mousePressEvent(QMouseEvent * pEvent) override; @@ -68,7 +70,8 @@ public: ~LogViewWindow(); protected: - KviPointerList<LogFile> m_logList; + std::vector<std::shared_ptr<LogFile>> m_logList; + std::vector<std::shared_ptr<LogFile>>::const_iterator m_currentLog; LogViewListView * m_pListView; @@ -104,18 +107,8 @@ protected: QTimer * m_pTimer; QMenu * m_pExportLogPopup; -public: - /** - * \brief Exports the log and creates the file in the selected format - * \param pLog The log file associated with the item selected in the popup - * \param iId The id of the item in the popup - * \param pszFile The buffer to store the exported log name - * \return void - */ - void createLog(LogFile * pLog, int iId, QString * pszFile = nullptr); - protected: - void exportLog(int iId); + void exportLog(LogFile::ExportType exportType); void recurseDirectory(const QString & szDir); void setupItemList(); diff --git a/src/modules/logview/libkvilogview.cpp b/src/modules/logview/libkvilogview.cpp index d4255726e..61056f370 100644 --- a/src/modules/logview/libkvilogview.cpp +++ b/src/modules/logview/libkvilogview.cpp @@ -32,6 +32,12 @@ #include "KviIconManager.h" #include "KviLocale.h" #include "KviApplication.h" +#include "KviOptions.h" +#include "KviFileUtils.h" +#include "KviFileDialog.h" + +#include <QString> +#include <QDate> static QRect g_rectLogViewGeometry; LogViewWindow * g_pLogViewWindow = nullptr; @@ -107,12 +113,31 @@ static bool logview_module_ctrl(KviModule *, const char * pcOperation, void * pP if(!pData) return false; - LogFile log{pData->szName}; - int iId = LogFile::PlainText; + LogFile log { pData->szName }; + LogFile::ExportType exportType = LogFile::PlainText; if(pData->szType == QLatin1String("html")) - iId = LogFile::HTML; + exportType = LogFile::HTML; + + QString szDate = log.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(log.typeString(), log.name(), log.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, + g_pLogViewWindow)) + return false; - g_pLogViewWindow->createLog(&log, iId, &(pData->szFile)); + log.createLog(exportType, szLog, &(pData->szFile)); return true; } |
