aboutsummaryrefslogtreecommitdiffstats
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/logview/CMakeLists.txt1
-rw-r--r--src/modules/logview/ExportOperation.cpp45
-rw-r--r--src/modules/logview/ExportOperation.h27
-rw-r--r--src/modules/logview/LogFile.cpp249
-rw-r--r--src/modules/logview/LogFile.h11
-rw-r--r--src/modules/logview/LogViewWidget.cpp14
-rw-r--r--src/modules/logview/LogViewWidget.h21
-rw-r--r--src/modules/logview/LogViewWindow.cpp330
-rw-r--r--src/modules/logview/LogViewWindow.h19
-rw-r--r--src/modules/logview/libkvilogview.cpp33
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(">&amp;!nc", ">&amp;");
+
+ 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;
}