1 //=============================================================================
2 //
3 // File : LogViewWindow.cpp
4 // Creation date : Tue Apr 23 2002 18:08:22 by Juanjo Alvarez
5 //
6 // This file is part of the KVIrc IRC client distribution
7 // Copyright (C) 2002 Juanjo Alvarez
8 // Copyright (C) 2002-2010 Szymon Stefanek (pragma at kvirc dot net)
9 // Copyright (C) 2011 Elvio Basello (hellvis69 at gmail dot com)
10 // Copyright (C) 2014 OmegaPhil (OmegaPhil at startmail dot com)
11 //
12 // This program is FREE software. You can redistribute it and/or
13 // modify it under the terms of the GNU General Public License
14 // as published by the Free Software Foundation; either version 2
15 // of the License, or (at your option) any later version.
16 //
17 // This program is distributed in the HOPE that it will be USEFUL,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20 // See the GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program. If not, write to the Free Software Foundation,
24 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 //=============================================================================
27
28 #include "LogViewWindow.h"
29 #include "LogViewWidget.h"
30
31 #include "KviHtmlGenerator.h"
32 #include "KviIconManager.h"
33 #include "KviLocale.h"
34 #include "KviModule.h"
35 #include "KviOptions.h"
36 #include "KviMainWindow.h"
37 #include "KviIrcView.h"
38 #include "KviQString.h"
39 #include "KviApplication.h"
40 #include "KviFileUtils.h"
41 #include "KviFileDialog.h"
42 #include "KviControlCodes.h"
43
44 #include <QList>
45 #include <QFileInfo>
46 #include <QDir>
47 #include <QCursor>
48 #include <QHeaderView>
49 #include <QPushButton>
50 #include <QDateEdit>
51 #include <QLineEdit>
52 #include <QLabel>
53 #include <QMouseEvent>
54 #include <QMessageBox>
55 #include <QProgressBar>
56 #include <QTextStream>
57 #include <QTabWidget>
58 #include <QCheckBox>
59 #include <QMenu>
60
61 #include <climits> //for INT_MAX
62
63 extern LogViewWindow * g_pLogViewWindow;
64
LogViewListView(QWidget * pParent)65 LogViewListView::LogViewListView(QWidget * pParent)
66 : QTreeWidget(pParent)
67 {
68 header()->setSortIndicatorShown(true);
69 setColumnCount(1);
70 setHeaderLabel(__tr2qs_ctx("Log File", "log"));
71 setSelectionMode(QAbstractItemView::SingleSelection);
72 setSortingEnabled(true);
73 setRootIsDecorated(true);
74 setAnimated(true);
75 }
76
mousePressEvent(QMouseEvent * pEvent)77 void LogViewListView::mousePressEvent(QMouseEvent * pEvent)
78 {
79 if(pEvent->button() == Qt::RightButton)
80 {
81 QTreeWidgetItem * pItem = itemAt(pEvent->pos());
82 if(pItem)
83 emit rightButtonPressed(pItem, QCursor::pos());
84 }
85 QTreeWidget::mousePressEvent(pEvent);
86 }
87
LogViewWindow()88 LogViewWindow::LogViewWindow()
89 : KviWindow(KviWindow::LogView, "log")
90 {
91 g_pLogViewWindow = this;
92 //m_pLogViewWidget = new KviLogViewWidget(this);
93
94 m_pSplitter = new KviTalSplitter(Qt::Horizontal, this);
95 m_pSplitter->setObjectName("main_splitter");
96 m_pSplitter->setChildrenCollapsible(false);
97
98 m_pLeftLayout = new KviTalVBox(m_pSplitter);
99 m_pTabWidget = new QTabWidget(m_pLeftLayout);
100 m_pBottomLayout = new KviTalHBox(m_pLeftLayout);
101 m_pProgressBar = new QProgressBar(m_pBottomLayout);
102
103 m_pCancelButton = new QPushButton(m_pBottomLayout);
104 m_pCancelButton->setText(__tr2qs_ctx("Cancel", "log"));
105 connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(abortFilter()));
106 m_pBottomLayout->setVisible(false);
107
108 m_pIndexTab = new KviTalVBox(m_pTabWidget);
109 m_pTabWidget->addTab(m_pIndexTab, __tr2qs_ctx("Index", "log"));
110
111 m_pListView = new LogViewListView(m_pIndexTab);
112
113 connect(m_pListView, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(itemSelected(QTreeWidgetItem *, QTreeWidgetItem *)));
114 connect(m_pListView, SIGNAL(rightButtonPressed(QTreeWidgetItem *, QPoint)), this, SLOT(rightButtonClicked(QTreeWidgetItem *, QPoint)));
115
116 m_pSearchTab = new QWidget(m_pTabWidget);
117 m_pTabWidget->addTab(m_pSearchTab, __tr2qs_ctx("Filter", "log"));
118
119 QGridLayout * pLayout = new QGridLayout(m_pSearchTab);
120
121 m_pShowChannelsCheck = new QCheckBox(__tr2qs_ctx("Show channel logs", "log"), m_pSearchTab);
122 m_pShowChannelsCheck->setChecked(true);
123 pLayout->addWidget(m_pShowChannelsCheck, 0, 0, 1, 2);
124
125 m_pShowQueryesCheck = new QCheckBox(__tr2qs_ctx("Show query logs", "log"), m_pSearchTab);
126 m_pShowQueryesCheck->setChecked(true);
127 pLayout->addWidget(m_pShowQueryesCheck, 1, 0, 1, 2);
128
129 m_pShowConsolesCheck = new QCheckBox(__tr2qs_ctx("Show console logs", "log"), m_pSearchTab);
130 m_pShowConsolesCheck->setChecked(true);
131 pLayout->addWidget(m_pShowConsolesCheck, 2, 0, 1, 2);
132
133 m_pShowDccChatCheck = new QCheckBox(__tr2qs_ctx("Show DCC chat logs", "log"), m_pSearchTab);
134 m_pShowDccChatCheck->setChecked(true);
135 pLayout->addWidget(m_pShowDccChatCheck, 3, 0, 1, 2);
136
137 m_pShowOtherCheck = new QCheckBox(__tr2qs_ctx("Show other logs", "log"), m_pSearchTab);
138 m_pShowOtherCheck->setChecked(true);
139 pLayout->addWidget(m_pShowOtherCheck, 4, 0, 1, 2);
140
141 QLabel * pLabel;
142 pLabel = new QLabel(__tr2qs_ctx("Contents Filter", "log"), m_pSearchTab);
143 pLayout->addWidget(pLabel, 5, 0, 1, 2);
144
145 pLabel = new QLabel(__tr2qs_ctx("Log name mask:", "log"), m_pSearchTab);
146 m_pFileNameMask = new QLineEdit(m_pSearchTab);
147 pLayout->addWidget(pLabel, 6, 0);
148 pLayout->addWidget(m_pFileNameMask, 6, 1);
149 connect(m_pFileNameMask, SIGNAL(returnPressed()), this, SLOT(applyFilter()));
150
151 pLabel = new QLabel(__tr2qs_ctx("Log contents mask:", "log"), m_pSearchTab);
152 m_pContentsMask = new QLineEdit(m_pSearchTab);
153 pLayout->addWidget(pLabel, 7, 0);
154 pLayout->addWidget(m_pContentsMask, 7, 1);
155 connect(m_pContentsMask, SIGNAL(returnPressed()), this, SLOT(applyFilter()));
156
157 m_pEnableFromFilter = new QCheckBox(__tr2qs_ctx("Only older than:", "log"), m_pSearchTab);
158 m_pFromDateEdit = new QDateEdit(m_pSearchTab);
159 m_pFromDateEdit->setDate(QDate::currentDate());
160 m_pFromDateEdit->setEnabled(false);
161 pLayout->addWidget(m_pEnableFromFilter, 8, 0);
162 pLayout->addWidget(m_pFromDateEdit, 8, 1);
163 connect(m_pEnableFromFilter, SIGNAL(toggled(bool)), m_pFromDateEdit, SLOT(setEnabled(bool)));
164
165 m_pEnableToFilter = new QCheckBox(__tr2qs_ctx("Only newer than:", "log"), m_pSearchTab);
166 m_pToDateEdit = new QDateEdit(m_pSearchTab);
167 m_pToDateEdit->setDate(QDate::currentDate());
168 m_pToDateEdit->setEnabled(false);
169 pLayout->addWidget(m_pEnableToFilter, 9, 0);
170 pLayout->addWidget(m_pToDateEdit, 9, 1);
171 connect(m_pEnableToFilter, SIGNAL(toggled(bool)), m_pToDateEdit, SLOT(setEnabled(bool)));
172
173 m_pFilterButton = new QPushButton(__tr2qs_ctx("Apply Filter", "log"), m_pSearchTab);
174 pLayout->addWidget(m_pFilterButton, 10, 1);
175 connect(m_pFilterButton, SIGNAL(clicked()), this, SLOT(applyFilter()));
176
177 QWidget * pWidget = new QWidget(m_pSearchTab);
178 pWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
179 pLayout->addWidget(pWidget, 11, 1);
180
181 m_pIrcView = new KviIrcView(m_pSplitter, this);
182 m_pIrcView->setMaxBufferSize(INT_MAX);
183 m_pIrcView->setFocusPolicy(Qt::ClickFocus);
184
185 QList<int> li;
186 li.append(110);
187 li.append(width() - 110);
188 m_pSplitter->setSizes(li);
189
190 // Using setData to track the option ordinal that used to be passed as id
191 m_pExportLogPopup = new QMenu("exportlog", this);
192 QAction * pAction = m_pExportLogPopup->addAction(__tr2qs_ctx("Plain Text File", "log"));
193 pAction->setData(LogFile::PlainText);
194 pAction = m_pExportLogPopup->addAction(__tr2qs_ctx("HTML Archive", "log"));
195 pAction->setData(LogFile::HTML);
196 //m_pExportLogPopup->addAction(__tr2qs_ctx("XML file","log"));
197 //m_pExportLogPopup->addAction(__tr2qs_ctx("database file","log"));
198
199 // 18.11.14: Originally this hooked activated, but Qt4 kept sending bullshit IDs here
200 // https://github.com/kvirc/KVIrc/issues/934#issuecomment-124933890
201 connect(m_pExportLogPopup, SIGNAL(triggered(QAction *)), this, SLOT(exportLog(QAction *)));
202
203 m_pTimer = new QTimer(this);
204 m_pTimer->setSingleShot(true);
205 m_pTimer->setInterval(0);
206 connect(m_pTimer, SIGNAL(timeout()), this, SLOT(filterNext()));
207 //avoid to execute the long time-consuming procedure of log indexing here:
208 //we could still be inside the context of the "Browse log files" QAction
209 QTimer::singleShot(0, this, SLOT(cacheFileList()));
210 }
211
~LogViewWindow()212 LogViewWindow::~LogViewWindow()
213 {
214 g_pLogViewWindow = nullptr;
215 }
216
keyPressEvent(QKeyEvent * pEvent)217 void LogViewWindow::keyPressEvent(QKeyEvent * pEvent)
218 {
219 if((pEvent->modifiers() & Qt::ControlModifier) || (pEvent->modifiers() & Qt::AltModifier) || (pEvent->modifiers() & Qt::MetaModifier))
220 {
221 if(pEvent->key() == Qt::Key_F)
222 {
223 m_pIrcView->toggleToolWidget();
224 return;
225 }
226 }
227 KviWindow::keyPressEvent(pEvent);
228 }
229
myIconPtr()230 QPixmap * LogViewWindow::myIconPtr()
231 {
232 return g_pIconManager->getSmallIcon(KviIconManager::Log);
233 }
234
resizeEvent(QResizeEvent *)235 void LogViewWindow::resizeEvent(QResizeEvent *)
236 {
237 m_pSplitter->setGeometry(0, 0, width(), height());
238 }
239
fillCaptionBuffers()240 void LogViewWindow::fillCaptionBuffers()
241 {
242 m_szPlainTextCaption = __tr2qs_ctx("Log Viewer", "log");
243 }
244
die()245 void LogViewWindow::die()
246 {
247 close();
248 }
249
sizeHint() const250 QSize LogViewWindow::sizeHint() const
251 {
252 QSize ret(m_pSplitter->sizeHint().width(), m_pIrcView->sizeHint().height());
253 return ret;
254 }
255
recurseDirectory(const QString & szDir)256 void LogViewWindow::recurseDirectory(const QString & szDir)
257 {
258 QDir dir(szDir);
259 QFileInfoList list = dir.entryInfoList();
260 for(int i = 0; i < list.count(); i++)
261 {
262 QFileInfo info = list[i];
263 if(info.isDir())
264 {
265 // recursive
266 if((info.fileName() != "..") && (info.fileName() != "."))
267 recurseDirectory(info.filePath());
268 }
269 else if((info.suffix() == "gz") || (info.suffix() == "log"))
270 {
271 m_logList.append(new LogFile(info.filePath()));
272 }
273 }
274 }
275
setupItemList()276 void LogViewWindow::setupItemList()
277 {
278 if(m_logList.isEmpty())
279 return;
280
281 m_pFilterButton->setEnabled(false);
282 m_pListView->clear();
283
284 m_bAborted = false;
285 m_pBottomLayout->setVisible(true);
286 m_pProgressBar->setRange(0, m_logList.count());
287 m_pProgressBar->setValue(0);
288
289 m_pLastCategory = nullptr;
290 m_pLastGroupItem = nullptr;
291 m_logList.first();
292 m_pTimer->start(); //singleshot
293 }
294
applyFilter()295 void LogViewWindow::applyFilter()
296 {
297 setupItemList();
298 }
299
abortFilter()300 void LogViewWindow::abortFilter()
301 {
302 m_bAborted = true;
303 }
304
filterNext()305 void LogViewWindow::filterNext()
306 {
307 QString szCurGroup;
308 LogFile * pFile = m_logList.current();
309 if(!pFile)
310 goto filter_last;
311
312 if(pFile->type() == LogFile::Channel && !m_pShowChannelsCheck->isChecked())
313 goto filter_next;
314 if(pFile->type() == LogFile::Console && !m_pShowConsolesCheck->isChecked())
315 goto filter_next;
316 if(pFile->type() == LogFile::DccChat && !m_pShowDccChatCheck->isChecked())
317 goto filter_next;
318 if(pFile->type() == LogFile::Other && !m_pShowOtherCheck->isChecked())
319 goto filter_next;
320 if(pFile->type() == LogFile::Query && !m_pShowQueryesCheck->isChecked())
321 goto filter_next;
322
323 if(m_pEnableFromFilter->isChecked())
324 if(pFile->date() > m_pFromDateEdit->date())
325 goto filter_next;
326
327 if(m_pEnableToFilter->isChecked())
328 if(pFile->date() < m_pToDateEdit->date())
329 goto filter_next;
330
331 if(!m_pFileNameMask->text().isEmpty())
332 if(!KviQString::matchString(m_pFileNameMask->text(), pFile->name()))
333 goto filter_next;
334
335 if(!m_pContentsMask->text().isEmpty())
336 {
337 QString szBuffer;
338 pFile->getText(szBuffer);
339 if(!KviQString::matchString(m_pContentsMask->text(), szBuffer))
340 goto filter_next;
341 }
342
343 if(m_pLastCategory)
344 {
345 if(m_pLastCategory->m_eType != pFile->type())
346 {
347 m_pLastCategory = nullptr;
348 for(int i = 0; i < m_pListView->topLevelItemCount(); ++i)
349 {
350 LogListViewItemType * pTmp = (LogListViewItemType *)m_pListView->topLevelItem(i);
351 if(pTmp->m_eType == pFile->type())
352 {
353 m_pLastCategory = pTmp;
354 break;
355 }
356 }
357 if(!m_pLastCategory)
358 m_pLastCategory = new LogListViewItemType(m_pListView, pFile->type());
359 }
360 }
361 else
362 {
363 m_pLastCategory = new LogListViewItemType(m_pListView, pFile->type());
364 }
365
366 szCurGroup = __tr2qs_ctx("%1 on %2", "log").arg(pFile->name(), pFile->network());
367
368 if(m_szLastGroup != szCurGroup)
369 {
370 m_szLastGroup = szCurGroup;
371 m_pLastGroupItem = nullptr;
372 for(int i = 0; i < m_pLastCategory->childCount(); ++i)
373 {
374 LogListViewItemFolder * pTmp = (LogListViewItemFolder *)m_pLastCategory->child(i);
375 if(pTmp->text(0) == m_szLastGroup)
376 {
377 m_pLastGroupItem = pTmp;
378 break;
379 }
380 }
381
382 if(!m_pLastGroupItem)
383 m_pLastGroupItem = new LogListViewItemFolder(m_pLastCategory, m_szLastGroup);
384 }
385
386 new LogListViewLog(m_pLastGroupItem, pFile->type(), pFile);
387
388 filter_next:
389 pFile = m_logList.next();
390
391 filter_last:
392 if(pFile && !m_bAborted)
393 {
394 m_pProgressBar->setValue(m_pProgressBar->value() + 1);
395 m_pTimer->start(); //singleshot
396 }
397 else
398 {
399 m_pBottomLayout->setVisible(false);
400 m_pListView->sortItems(0, Qt::AscendingOrder);
401 m_pProgressBar->setValue(0);
402 m_pFilterButton->setEnabled(true);
403
404 // Reset m_szLastGroup for next search
405 m_szLastGroup = "";
406 }
407 }
408
cacheFileList()409 void LogViewWindow::cacheFileList()
410 {
411 QString szLogPath;
412 g_pApp->getLocalKvircDirectory(szLogPath, KviApplication::Log);
413 recurseDirectory(szLogPath);
414
415 setupItemList();
416 }
417
itemSelected(QTreeWidgetItem * it,QTreeWidgetItem *)418 void LogViewWindow::itemSelected(QTreeWidgetItem * it, QTreeWidgetItem *)
419 {
420 //A parent node
421 m_pIrcView->clearBuffer();
422 if(!it || !it->parent() || !(((LogListViewItem *)it)->m_pFileData))
423 return;
424
425 QString szText;
426 ((LogListViewItem *)it)->m_pFileData->getText(szText);
427
428 QStringList lines = szText.split('\n');
429 bool bOk;
430 int iMsgType;
431 for(auto & line : lines)
432 {
433 QString szNum = line.section(' ', 0, 0);
434 iMsgType = szNum.toInt(&bOk);
435 if(iMsgType < 0 || iMsgType > (KVI_NUM_MSGTYPE_OPTIONS - 1))
436 iMsgType = 0;
437 if(bOk)
438 outputNoFmt(iMsgType, line.section(' ', 1), KviIrcView::NoRepaint | KviIrcView::NoTimestamp);
439 else
440 outputNoFmt(0, line, KviIrcView::NoRepaint | KviIrcView::NoTimestamp);
441 }
442 m_pIrcView->repaint();
443 }
444
rightButtonClicked(QTreeWidgetItem * pItem,const QPoint &)445 void LogViewWindow::rightButtonClicked(QTreeWidgetItem * pItem, const QPoint &)
446 {
447 if(!pItem)
448 return;
449 m_pListView->setCurrentItem(pItem);
450
451 QMenu * pPopup = new QMenu(this);
452 if(((LogListViewItem *)pItem)->childCount())
453 {
454 // TODO: probably should allow to specify a directory instead of asking for file path on each log file
455 pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Save)), __tr2qs_ctx("Export All Log Files to", "log"))->setMenu(m_pExportLogPopup);
456 pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)), __tr2qs_ctx("Remove All Log Files Within This Folder", "log"), this, SLOT(deleteCurrent()));
457 }
458 else
459 {
460 pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Save)), __tr2qs_ctx("Export Log File to", "log"))->setMenu(m_pExportLogPopup);
461 pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)), __tr2qs_ctx("Remove Log File", "log"), this, SLOT(deleteCurrent()));
462 }
463
464 pPopup->exec(QCursor::pos());
465 }
466
deleteCurrent()467 void LogViewWindow::deleteCurrent()
468 {
469 LogListViewItem * pItem = dynamic_cast<LogListViewItem *>(m_pListView->currentItem());
470 if(!pItem)
471 return;
472
473 if(!pItem->childCount())
474 {
475 if(!pItem->fileName().isNull())
476 {
477 if(QMessageBox::question(
478 this,
479 __tr2qs_ctx("Confirm Current User Log Deletion", "log"),
480 __tr2qs_ctx("Do you really wish to delete this log?", "log"),
481 __tr2qs("Yes"), __tr2qs("No"), nullptr, 1)
482 != 0)
483 return;
484
485 KviFileUtils::removeFile(pItem->fileName());
486 if(!pItem->parent()->childCount())
487 delete pItem->parent();
488
489 delete pItem;
490 m_pIrcView->clearBuffer();
491 }
492 return;
493 }
494
495 if(QMessageBox::question(
496 this,
497 __tr2qs_ctx("Confirm Current User Logs Deletion", "log"),
498 __tr2qs_ctx("Do you really wish to delete all these logs?", "log"),
499 __tr2qs("Yes"), __tr2qs("No"), nullptr, 1)
500 != 0)
501 return;
502 KviPointerList<LogListViewItem> itemsList;
503 itemsList.setAutoDelete(false);
504 for(int i = 0; i < pItem->childCount(); i++)
505 {
506 if(!pItem->child(i)->childCount())
507 {
508 itemsList.append((LogListViewItem *)pItem->child(i));
509 continue;
510 }
511 LogListViewItem * pChild = (LogListViewItem *)pItem->child(i);
512 for(int j = 0; j < pChild->childCount(); j++)
513 {
514 if(!(LogListViewItem *)pChild->child(j))
515 {
516 qDebug("Null pointer in logviewitem");
517 continue;
518 }
519 itemsList.append((LogListViewItem *)pChild->child(j));
520 }
521 }
522 for(unsigned int u = 0; u < itemsList.count(); u++)
523 {
524 LogListViewItem * pCurItem = itemsList.at(u);
525 if(!pCurItem->fileName().isNull())
526 KviFileUtils::removeFile(pCurItem->fileName());
527 }
528 delete pItem;
529 }
530
exportLog(QAction * pAction)531 void LogViewWindow::exportLog(QAction * pAction)
532 {
533 /* This slot is required as Qt4 has started to screw up the menu ordinal
534 * sent with the old activated signal - the ordinal is now stored as
535 * QAction user data */
536 if(pAction)
537 exportLog(pAction->data().toInt());
538 else
539 qDebug("LogViewWindow::exportLog called with invalid pAction");
540 }
541
exportLog(int iId)542 void LogViewWindow::exportLog(int iId)
543 {
544 LogListViewItem * pItem = (LogListViewItem *)(m_pListView->currentItem());
545 if(!pItem)
546 return;
547
548 if(!pItem->childCount())
549 {
550 // Export the log
551 createLog(pItem->log(), iId);
552 return;
553 }
554
555 // We selected a node in the log list, scan the children
556 KviPointerList<LogListViewItem> logList;
557 logList.setAutoDelete(false);
558 for(int i = 0; i < pItem->childCount(); i++)
559 {
560 if(!pItem->child(i)->childCount())
561 {
562 // The child is a log file, append it to the list
563 logList.append((LogListViewItem *)pItem->child(i));
564 continue;
565 }
566
567 // The child is a node, scan it
568 LogListViewItem * pChild = (LogListViewItem *)pItem->child(i);
569 for(int j = 0; j < pChild->childCount(); j++)
570 {
571 if(!(LogListViewItem *)pChild->child(j))
572 {
573 qDebug("Null pointer in logviewitem");
574 continue;
575 }
576
577 // Add the child to the list
578 logList.append((LogListViewItem *)pChild->child(j));
579 }
580 }
581
582 // Scan the list
583 for(unsigned int u = 0; u < logList.count(); u++)
584 {
585 LogListViewItem * pCurItem = logList.at(u);
586 createLog(pCurItem->log(), iId);
587 }
588 }
589
createLog(LogFile * pLog,int iId,QString * pszFile)590 void LogViewWindow::createLog(LogFile * pLog, int iId, QString * pszFile)
591 {
592 if(!pLog)
593 return;
594
595 QRegExp rx;
596 QString szLog, szLogDir, szInputBuffer, szOutputBuffer, szLine, szTmp;
597 QString szDate = pLog->date().toString("yyyy.MM.dd");
598
599 /* Fetching previous export path and concatenating with generated filename
600 * adjustFilePath is for file paths not directory paths */
601 szLog = KVI_OPTION_STRING(KviOption_stringLogsExportPath).trimmed();
602 if(!szLog.isEmpty())
603 szLog += KVI_PATH_SEPARATOR_CHAR;
604 szLog += QString("%1_%2.%3_%4").arg(pLog->typeString(), pLog->name(), pLog->network(), szDate);
605 KviFileUtils::adjustFilePath(szLog);
606
607 // Getting output file path from the user, with overwrite confirmation
608 if(!KviFileDialog::askForSaveFileName(
609 szLog,
610 __tr2qs_ctx("Export Log - KVIrc", "log"),
611 szLog,
612 QString(),
613 false,
614 true,
615 true,
616 this))
617 return;
618
619 /* Save export directory - this directory path is also used in the HTML export
620 * and info is used when working with pszFile */
621 QFileInfo info(szLog);
622 szLogDir = info.absoluteDir().absolutePath();
623 KVI_OPTION_STRING(KviOption_stringLogsExportPath) = szLogDir;
624
625 /* Reading in log file - LogFiles are read in as bytes, so '\r' isn't
626 * sanitised by default */
627 pLog->getText(szInputBuffer);
628 QStringList lines = szInputBuffer.replace('\r', "").split('\n');
629
630 switch(iId)
631 {
632 case LogFile::PlainText:
633 {
634 /* Only append extension if it isn't there already (e.g. a specific
635 * file is to be overwritten) */
636 if(!szLog.endsWith(".txt"))
637 szLog += ".txt";
638
639 // Scan the file
640 for(auto & line : lines)
641 {
642 szTmp = line;
643 szLine = KviControlCodes::stripControlBytes(szTmp);
644
645 // Remove icons' code
646 rx.setPattern("^\\d{1,3}\\s");
647 szLine.replace(rx, "");
648
649 // Remove link from a user speaking, deal with (and keep) various ranks
650 // e.g.: <!ncHelLViS69> --> <HelLViS69>
651 rx.setPattern("\\s<([+%@&~!]?)!nc");
652 szLine.replace(rx, " <\\1");
653
654 // Remove link from a nick in a mask
655 // e.g.: !nFoo [~bar@!hfoo.bar] --> Foo [~bar@!hfoo.bar]
656 rx.setPattern("\\s!n");
657 szLine.replace(rx, " ");
658
659 // Remove link from a host in a mask
660 // e.g.: Foo [~bar@!hfoo.bar] --> Foo [~bar@foo.bar]
661 rx.setPattern("@!h");
662 szLine.replace(rx, "@");
663
664 // Remove link from a channel
665 // e.g.: !c#KVIrc --> #KVIrc
666 rx.setPattern("!c#");
667 szLine.replace(rx, "#");
668
669 szOutputBuffer += szLine;
670 szOutputBuffer += "\n";
671 }
672
673 break;
674 }
675 case LogFile::HTML:
676 {
677 /* Only append extension if it isn't there already (e.g. a specific
678 * file is to be overwritten) */
679 if(!szLog.endsWith(".html"))
680 szLog += ".html";
681
682 szTmp = QString("KVIrc %1 %2").arg(KVI_VERSION).arg(KVI_RELEASE_NAME);
683 QString szNick = "";
684 bool bFirstLine = true;
685
686 QString szTitle;
687 switch(pLog->type())
688 {
689 case LogFile::Channel:
690 szTitle = __tr2qs_ctx("Channel %1 on %2", "log").arg(pLog->name(), pLog->network());
691 break;
692 case LogFile::Console:
693 szTitle = __tr2qs_ctx("Console on %1", "log").arg(pLog->network());
694 break;
695 case LogFile::Query:
696 szTitle = __tr2qs_ctx("Query with: %1 on %2", "log").arg(pLog->name(), pLog->network());
697 break;
698 case LogFile::DccChat:
699 szTitle = __tr2qs_ctx("DCC Chat with: %1", "log").arg(pLog->name());
700 break;
701 case LogFile::Other:
702 szTitle = __tr2qs_ctx("Something on: %1", "log").arg(pLog->network());
703 break;
704 }
705
706 // Prepare HTML document
707 szOutputBuffer += "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n";
708 szOutputBuffer += "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n";
709 szOutputBuffer += "<head>\n";
710 szOutputBuffer += "\t<meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=utf-8\" />\n";
711 szOutputBuffer += "\t<meta name=\"author\" content=\"" + szTmp + "\" />\n";
712 szOutputBuffer += "\t<title>" + szTitle + "</title>\n";
713 szOutputBuffer += "</head>\n<body>\n";
714 szOutputBuffer += "<h2>" + szTitle + "</h2>\n<h3>Date: " + szDate + "</h3>\n";
715
716 // Scan the file
717 for(auto & line : lines)
718 {
719 szTmp = line;
720
721 // Find who has talked
722 QString szTmpNick = szTmp.section(" ", 2, 2);
723 if((szTmpNick.left(1) != "<") && (szTmpNick.right(1) != ">"))
724 szTmpNick = "";
725
726 // locate msgtype
727 QString szNum = szTmp.section(' ', 0, 0);
728 bool bOk;
729 int iMsgType = szNum.toInt(&bOk);
730
731 // only human text for now...
732 if(iMsgType != 24 && iMsgType != 25 && iMsgType != 26)
733 continue;
734
735 // remove msgtype tag
736 szTmp = szTmp.remove(0, szNum.length() + 1);
737
738 szTmp = KviHtmlGenerator::convertToHtml(szTmp, true);
739
740 // insert msgtype icon at start of the current text line
741 KviMessageTypeSettings msg(KVI_OPTION_MSGTYPE(iMsgType));
742 QString szIcon = g_pIconManager->getSmallIconResourceName((KviIconManager::SmallIcon)msg.pixId());
743 szTmp.prepend("<img src=\"" + szIcon + R"(" alt="" /> )");
744
745 /*
746 * Check if the nick who has talked is the same of the above line.
747 * If so, we have to put the line as it is, otherwise we have to
748 * open a new paragraph
749 */
750 if(szTmpNick != szNick)
751 {
752 /*
753 * People is not the same, close the paragraph opened
754 * before and open a new one
755 */
756 if(!bFirstLine)
757 szOutputBuffer += "</p>\n";
758 szTmp.prepend("<p>");
759
760 szNick = szTmpNick;
761 }
762 else
763 {
764 // Break the line
765 szTmp.prepend("<br />\n");
766 }
767
768 szOutputBuffer += szTmp;
769 bFirstLine = false;
770 }
771
772 // Close the last paragraph
773 szOutputBuffer += "</p>\n";
774
775 // regexp to search all embedded icons
776 rx.setPattern("<img src=\"smallicons:([^\"]+)");
777 int iIndex = szOutputBuffer.indexOf(rx);
778 QStringList szImagesList;
779
780 // search for icons
781 while(iIndex >= 0)
782 {
783 int iLength = rx.matchedLength();
784 QString szCap = rx.cap(1);
785
786 // if the icon isn't in the images list then add
787 if(szImagesList.indexOf(szCap) == -1)
788 szImagesList.append(szCap);
789 iIndex = szOutputBuffer.indexOf(rx, iIndex + iLength);
790 }
791
792 // get current theme path
793 QString szCurrentThemePath;
794 g_pApp->getLocalKvircDirectory(szCurrentThemePath, KviApplication::Themes, KVI_OPTION_STRING(KviOption_stringIconThemeSubdir));
795 szCurrentThemePath += KVI_PATH_SEPARATOR_CHAR;
796
797 // current coresmall path
798 szCurrentThemePath += "coresmall";
799 szCurrentThemePath += KVI_PATH_SEPARATOR_CHAR;
800
801 // check if coresmall exists in current theme
802 if(!KviFileUtils::directoryExists(szCurrentThemePath))
803 {
804 // get global coresmall path
805 g_pApp->getGlobalKvircDirectory(szCurrentThemePath, KviApplication::Pics, "coresmall");
806 KviQString::ensureLastCharIs(szCurrentThemePath, QChar(KVI_PATH_SEPARATOR_CHAR));
807 }
808
809 // copy all icons to the log destination folder
810 for(int i = 0; i < szImagesList.count(); i++)
811 {
812 QString szSourceFile = szCurrentThemePath + szImagesList.at(i);
813 QString szDestFile = szLogDir + szImagesList.at(i);
814 KviFileUtils::copyFile(szSourceFile, szDestFile);
815 }
816
817 // remove internal tags
818 rx.setPattern("<qt>|</qt>|smallicons:");
819 szOutputBuffer.replace(rx, "");
820 szOutputBuffer.replace(">!nc", ">");
821 szOutputBuffer.replace("@!nc", "@");
822 szOutputBuffer.replace("%!nc", "%");
823
824 // Close the document
825 szOutputBuffer += "</body>\n</html>\n";
826
827 break;
828 }
829 }
830
831 // File overwriting already dealt with when file path was obtained
832 QFile log(szLog);
833 if(!log.open(QIODevice::WriteOnly | QIODevice::Text))
834 return;
835
836 if(pszFile)
837 {
838 *pszFile = "";
839 *pszFile = info.filePath();
840 }
841
842 // Ensure we're writing in UTF-8
843 QTextStream output(&log);
844 output.setCodec("UTF-8");
845 output << szOutputBuffer;
846
847 // Close file descriptors
848 log.close();
849 }
850