[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [vidalia/alpha] option to filter message log by search term
commit 69f9bd84027043bfd87f749d4e61ccd5bdfdb1c1
Author: Sebastian Baginski <sebthestampede@xxxxxxxxx>
Date: Sat Mar 24 00:51:49 2012 +0100
option to filter message log by search term
---
changes/bug2733 | 2 +
src/vidalia/CMakeLists.txt | 1 +
src/vidalia/log/LogFilter.cpp | 241 +++++++++++++++++++++++++++++++++++++
src/vidalia/log/LogFilter.h | 70 +++++++++++
src/vidalia/log/LogTreeWidget.cpp | 16 ++-
src/vidalia/log/LogTreeWidget.h | 13 +-
src/vidalia/log/MessageLog.cpp | 65 ++++++++++-
src/vidalia/log/MessageLog.h | 14 ++-
src/vidalia/log/MessageLog.ui | 29 ++++-
9 files changed, 428 insertions(+), 23 deletions(-)
diff --git a/changes/bug2733 b/changes/bug2733
new file mode 100644
index 0000000..c11dcb6
--- /dev/null
+++ b/changes/bug2733
@@ -0,0 +1,2 @@
+ o Add option to use search query as message log filter. Fixes
+ ticket 2733.
\ No newline at end of file
diff --git a/src/vidalia/CMakeLists.txt b/src/vidalia/CMakeLists.txt
index c399df0..86f755a 100644
--- a/src/vidalia/CMakeLists.txt
+++ b/src/vidalia/CMakeLists.txt
@@ -303,6 +303,7 @@ set(vidalia_SRCS ${vidalia_SRCS}
log/StatusEventItem.cpp
log/StatusEventItemDelegate.cpp
log/StatusEventWidget.cpp
+ log/LogFilter.cpp
)
qt4_wrap_cpp(vidalia_SRCS
log/LogFile.h
diff --git a/src/vidalia/log/LogFilter.cpp b/src/vidalia/log/LogFilter.cpp
new file mode 100644
index 0000000..d7b64d6
--- /dev/null
+++ b/src/vidalia/log/LogFilter.cpp
@@ -0,0 +1,241 @@
+/*
+** This file is part of Vidalia, and is subject to the license terms in the
+** LICENSE file, found in the top level directory of this distribution. If you
+** did not receive the LICENSE file with this file, you may obtain it from the
+** Vidalia source package distributed by the Vidalia Project at
+** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
+** including this file, may be copied, modified, propagated, or distributed
+** except according to the terms described in the LICENSE file.
+*/
+
+/*
+** \file LogFilter.cpp
+** \brief Object used to filter message log history
+*/
+
+#include "LogFilter.h"
+#include <QStack>
+
+/** Constructor taking severity as argument. */
+LogFilter::LogFilter(uint filter)
+ : _filter(filter)
+{
+}
+
+/** Default destructor. */
+LogFilter::~LogFilter()
+{
+}
+
+/** Evaluate if message should be included into log history.
+ * \param type Message severity
+ * \param message Message
+ */
+bool
+LogFilter::eval(tc::Severity type, const QString &message) const
+{
+ Q_UNUSED(message);
+ return (_filter & (uint)type);
+}
+
+/** Constructor regular expression and severity. */
+LogFilterRegExp::LogFilterRegExp(const QRegExp &exp, uint filter)
+ :LogFilter(filter)
+ ,_regExp(exp)
+{
+
+}
+
+/** Evaluate if message should be included into log history.
+ * \param type Message severity
+ * \param message Message
+ */
+bool
+LogFilterRegExp::eval(tc::Severity type, const QString &message) const
+{
+ if (LogFilter::eval(type, message))
+ return _regExp.isValid() ? message.contains(_regExp) : true;
+ return false;
+}
+
+/** Helper class for search expression tree. */
+class LogFilterSearchTerm::ExpTree {
+public:
+ ExpTree(const QString& value=QString())
+ {
+ _value = value;
+ _left = NULL;
+ _right = NULL;
+ _invert = false;
+ }
+
+ ~ExpTree()
+ {
+ if (_left)
+ delete _left;
+ if (_right)
+ delete _right;
+ }
+
+ bool eval(const QString& input)
+ {
+ if (_value.isEmpty())
+ return true;
+ bool result;
+ if (_value == "|") {
+ if (!_left || !_right)
+ return false;
+ result = _left->eval(input) || _right->eval(input);
+ } else if (_value == "&") {
+ if (!_left || !_right)
+ return false;
+ result = _left->eval(input) && _right->eval(input);
+ } else {
+ result = input.contains(_value);
+ }
+ return _invert ? !result : result;
+ }
+
+private:
+ /** Stores the value of this node, can be a string or operator. */
+ QString _value;
+ /** Remembers if the result of this node evaluation should be inverted. */
+ bool _invert;
+ /** Left child node. */
+ ExpTree * _left;
+ /** Right child node. */
+ ExpTree * _right;
+
+ friend class LogFilterSearchTerm;
+};
+
+/** Constructor taking search term and severity as arguments. */
+LogFilterSearchTerm::LogFilterSearchTerm(const QString &term, uint filter)
+ :LogFilter(filter)
+ ,_expressionTree(NULL)
+{
+ parseString(term);
+}
+
+/** Default destructor. */
+LogFilterSearchTerm::~LogFilterSearchTerm()
+{
+ if (_expressionTree)
+ delete _expressionTree;
+}
+
+/** Evaluate if message should be included into log history.
+ * \param type Message severity
+ * \param msg Message
+ */
+bool
+LogFilterSearchTerm::eval(tc::Severity type, const QString &msg) const
+{
+ if (LogFilter::eval(type, msg))
+ return _expressionTree->eval(msg);
+ return false;
+}
+
+/** Helper method that inserts string into list and clears it. */
+void
+addToQueue(QList<QString>& queue, QString& token)
+{
+ if (!token.isEmpty()) {
+ queue.append(token);
+ token.clear();
+ }
+}
+
+/** Helper method used to parse input string and create regular expression.
+ * It uses 'Shunting-yard algorithm' to build reverse polish notation of
+ * the input expression. This output is then converted to expression tree
+ * for evaluation.
+ */
+void
+LogFilterSearchTerm::parseString(const QString& input)
+{
+ QStack<QString> stack; /* Stack for operators */
+ QList<QString> outputQueue; /* RPN output */
+ QString pattern = input;
+ pattern.replace("OR","|");
+ pattern.replace("AND","&");
+ pattern.replace("NOT","!");
+
+ /* Step 1: create RPN of input expression. */
+ QString token;
+ foreach (const QChar& c, pattern) {
+ if (c.isSpace())
+ continue;
+ if (c=='!' || c=='&' || c=='|') {
+ addToQueue(outputQueue, token);
+ /* Checking operator precedence with op on top of the stack. */
+ while (!stack.isEmpty()) {
+ QString op = stack.top();
+ if (c=='&' || c=='|') {
+ if (op=="!") {
+ addToQueue(outputQueue, op);
+ stack.pop();
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ stack.push(c);
+ } else if (c=='(') {
+ addToQueue(outputQueue, token);
+ stack.push(c);
+ } else if (c==')') {
+ addToQueue(outputQueue, token);
+ while (!stack.isEmpty()) {
+ QString tmp = stack.pop();
+ if (tmp=="(") {
+ break;
+ } else {
+ addToQueue(outputQueue, tmp);
+ }
+ }
+ } else {
+ token += c;
+ }
+ }
+ addToQueue(outputQueue, token);
+ while (!stack.isEmpty()) {
+ QString top = stack.pop();
+ addToQueue(outputQueue, top);
+ }
+
+ /* Step 2: convert RPN expression to binary tree. */
+ QStack<ExpTree*> tree;
+ foreach (QString s, outputQueue) {
+ if (s == "!") {
+ if (tree.isEmpty()) {
+ break;
+ }
+ ExpTree * top = tree.top();
+ top->_invert = !top->_invert;
+ } else if (s == "&" || s == "|") {
+ if (tree.count() < 2) {
+ qDeleteAll(tree);
+ tree.clear();
+ break;
+ }
+ ExpTree * node = new ExpTree(s);
+ node->_left = tree.pop();
+ node->_right = tree.pop();
+ tree.push(node);
+ } else {
+ tree.push(new ExpTree(s));
+ }
+ }
+ if (_expressionTree)
+ delete _expressionTree;
+ /* If everything is ok, there should be only one item left on stack */
+ if (tree.count() == 1) {
+ _expressionTree = tree.top();
+ } else {
+ qDeleteAll(tree);
+ _expressionTree = new ExpTree();
+ }
+}
diff --git a/src/vidalia/log/LogFilter.h b/src/vidalia/log/LogFilter.h
new file mode 100644
index 0000000..f1bb415
--- /dev/null
+++ b/src/vidalia/log/LogFilter.h
@@ -0,0 +1,70 @@
+/*
+** This file is part of Vidalia, and is subject to the license terms in the
+** LICENSE file, found in the top level directory of this distribution. If you
+** did not receive the LICENSE file with this file, you may obtain it from the
+** Vidalia source package distributed by the Vidalia Project at
+** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
+** including this file, may be copied, modified, propagated, or distributed
+** except according to the terms described in the LICENSE file.
+*/
+
+/*
+** \file LogFilter.h
+** \brief Object used to filter message log history
+*/
+
+#ifndef _LOGFILTER_H
+#define _LOGFILTER_H
+
+#include "tcglobal.h"
+#include <QRegExp>
+
+/** Base class for objects used to filter log history. */
+class LogFilter {
+
+public:
+ /** Constructor taking severity filter as argument. */
+ LogFilter(uint filter);
+ virtual ~LogFilter();
+ /** Method used to evaluate given log message. */
+ virtual bool eval(tc::Severity type, const QString& message) const;
+
+private:
+ /** Stores severity filter */
+ uint _filter;
+};
+
+
+/** Message filter based on regular expression. */
+class LogFilterRegExp : public LogFilter {
+
+public:
+ LogFilterRegExp(const QRegExp& exp, uint filter);
+ bool eval(tc::Severity type, const QString &message) const;
+
+private:
+ /** Stores regular expression used to filter messages. */
+ QRegExp _regExp;
+};
+
+
+/** Message filter based on simple search query. */
+class LogFilterSearchTerm : public LogFilter {
+
+public:
+ LogFilterSearchTerm(const QString& term, uint filter);
+ ~LogFilterSearchTerm();
+ bool eval(tc::Severity type, const QString &message) const;
+
+private:
+ class ExpTree;
+
+ /** Helper method used to create expression tree from given query. */
+ void parseString(const QString& input);
+
+ /** Stores the expression tree for filtering messages. */
+ ExpTree * _expressionTree;
+};
+
+#endif
+
diff --git a/src/vidalia/log/LogTreeWidget.cpp b/src/vidalia/log/LogTreeWidget.cpp
index eb20bea..2b8aeb2 100644
--- a/src/vidalia/log/LogTreeWidget.cpp
+++ b/src/vidalia/log/LogTreeWidget.cpp
@@ -60,7 +60,7 @@ LogTreeWidget::verticalSliderReleased()
/** Cast a QList of QTreeWidgetItem pointers to a list of LogTreeWidget
* pointers. There really must be a better way to do this. */
QList<LogTreeItem *>
-LogTreeWidget::qlist_cast(QList<QTreeWidgetItem *> inlist)
+LogTreeWidget::qlist_cast(QList<QTreeWidgetItem *> inlist) const
{
QList<LogTreeItem *> outlist;
foreach (QTreeWidgetItem *item, inlist) {
@@ -71,7 +71,7 @@ LogTreeWidget::qlist_cast(QList<QTreeWidgetItem *> inlist)
/** Sorts the list of pointers to log tree items by timestamp. */
QList<LogTreeItem *>
-LogTreeWidget::qlist_sort(QList<LogTreeItem *> inlist)
+LogTreeWidget::qlist_sort(QList<LogTreeItem *> inlist) const
{
QMap<quint32, LogTreeItem *> outlist;
foreach (LogTreeItem *item, inlist) {
@@ -106,7 +106,7 @@ LogTreeWidget::clearMessages()
/** Returns a list of all currently selected items. */
QStringList
-LogTreeWidget::selectedMessages()
+LogTreeWidget::selectedMessages() const
{
QStringList messages;
@@ -123,7 +123,7 @@ LogTreeWidget::selectedMessages()
/** Returns a list of all items in the tree. */
QStringList
-LogTreeWidget::allMessages()
+LogTreeWidget::allMessages() const
{
QStringList messages;
@@ -229,12 +229,16 @@ LogTreeWidget::addLogTreeItem(LogTreeItem *item)
/** Filters the message log based on the given filter. */
void
-LogTreeWidget::filter(uint filter)
+LogTreeWidget::filter(LogFilter * filter)
{
+ if (!filter) {
+ return;
+ }
int itemsShown = 0;
for (int i = _itemHistory.size()-1; i >= 0; i--) {
LogTreeItem *item = _itemHistory.at(i);
- if ((itemsShown < _maxItemCount) && (filter & item->severity())) {
+ tc::Severity type = item->severity();
+ if ((itemsShown < _maxItemCount) && filter->eval(type,item->message())) {
itemsShown++;
} else {
int itemIndex = indexOfTopLevelItem(item);
diff --git a/src/vidalia/log/LogTreeWidget.h b/src/vidalia/log/LogTreeWidget.h
index 298038d..c87f3d4 100644
--- a/src/vidalia/log/LogTreeWidget.h
+++ b/src/vidalia/log/LogTreeWidget.h
@@ -17,6 +17,7 @@
#define _LOGTREEWIDGET_H
#include "LogTreeItem.h"
+#include "LogFilter.h"
#include "TorControl.h"
@@ -44,9 +45,9 @@ public:
LogTreeWidget(QWidget *parent = 0);
/** Returns a list of all currently selected messages. */
- QStringList selectedMessages();
+ QStringList selectedMessages() const;
/** Returns a list of all messages in the tree. */
- QStringList allMessages();
+ QStringList allMessages() const;
/** Deselects all currently selected messages. */
void deselectAll();
@@ -55,8 +56,8 @@ public:
/** Sets the maximum number of items in the tree. */
void setMaximumMessageCount(int max);
/** Filters the log according to the specified filter. */
- void filter(uint filter);
-
+ void filter(LogFilter * filter);
+
/** Adds a log item to the tree. */
LogTreeItem* log(tc::Severity severity, const QString &message);
@@ -79,9 +80,9 @@ private:
/** Adds <b>item</b> as a top-level item in the tree. */
void addLogTreeItem(LogTreeItem *item);
/** Casts a QList of one pointer type to another. */
- QList<LogTreeItem *> qlist_cast(QList<QTreeWidgetItem *> inlist);
+ QList<LogTreeItem *> qlist_cast(QList<QTreeWidgetItem *> inlist) const;
/** Sortrs a QList of pointers to tree items. */
- QList<LogTreeItem *> qlist_sort(QList<LogTreeItem *> inlist);
+ QList<LogTreeItem *> qlist_sort(QList<LogTreeItem *> inlist) const;
/**< List of pointers to all log message items currently in the tree. */
QList<LogTreeItem *> _itemHistory;
diff --git a/src/vidalia/log/MessageLog.cpp b/src/vidalia/log/MessageLog.cpp
index 2da0d32..f4bfe07 100644
--- a/src/vidalia/log/MessageLog.cpp
+++ b/src/vidalia/log/MessageLog.cpp
@@ -30,6 +30,7 @@
/* Message log settings */
#define SETTING_MSG_FILTER "MessageFilter"
+#define SETTING_FILTER_TERM "MessageFilterSearchTerm"
#define SETTING_MAX_MSG_COUNT "MaxMsgCount"
#define SETTING_ENABLE_LOGFILE "EnableLogFile"
#define SETTING_LOGFILE "LogFile"
@@ -45,6 +46,7 @@
#else
#define DEFAULT_LOGFILE (QDir::homePath() + "/.tor/tor-log.txt")
#endif
+#define DEFAULT_FILTER_TERM ""
#define ADD_TO_FILTER(f,v,b) (f = ((b) ? ((f) | (v)) : ((f) & ~(v))))
@@ -58,6 +60,7 @@
*/
MessageLog::MessageLog(QStatusBar *st, QWidget *parent)
: VidaliaTab(tr("Message Log"), "MessageLog", parent),
+ _logFilter(NULL),
_statusBar(st)
{
/* Invoke Qt Designer generated QObject setup routine */
@@ -89,6 +92,8 @@ MessageLog::MessageLog(QStatusBar *st, QWidget *parent)
MessageLog::~MessageLog()
{
_logFile.close();
+ if (_logFilter)
+ delete _logFilter;
}
/** Binds events (signals) to actions (slots). */
@@ -162,6 +167,13 @@ MessageLog::setToolTips()
"during normal Tor operation."));
ui.chkTorDebug->setToolTip(tr("Hyper-verbose messages primarily of \n"
"interest to Tor developers."));
+ ui.chkFilterSearch->setToolTip(tr("Custom filter based on search term."));
+ ui.lineSearchTerm->setToolTip(tr("Here you can type a search term used \n"
+ "to filter log messages. Recognized "
+ "keywords: \nNOT, AND, OR.\n"
+ "You can use brackets for grouping.\n"
+ "To input regular expression in perl\n"
+ "syntax, use \\r prefix."));
}
/** Called when the user changes the UI translation. */
@@ -201,9 +213,16 @@ MessageLog::loadSettings()
ui.chkTorDebug->setChecked(_filter & tc::DebugSeverity);
registerLogEvents();
+ /* Load custom search term filter */
+ const QString term = getSetting(SETTING_FILTER_TERM, DEFAULT_FILTER_TERM)
+ .toString();
+ ui.chkFilterSearch->setChecked(!term.isEmpty());
+ ui.lineSearchTerm->setText(term);
+ buildMessageFilter(term);
+
/* Filter the message log */
QApplication::setOverrideCursor(Qt::WaitCursor);
- ui.listMessages->filter(_filter);
+ ui.listMessages->filter(_logFilter);
QApplication::restoreOverrideCursor();
}
@@ -290,9 +309,17 @@ MessageLog::saveSettings()
saveSetting(SETTING_MSG_FILTER, filter);
registerLogEvents();
+ /* Save search term filter */
+ QString term;
+ if (ui.chkFilterSearch->isChecked()) {
+ term = ui.lineSearchTerm->text();
+ }
+ saveSetting(SETTING_FILTER_TERM, term);
+ buildMessageFilter(term);
+
/* Filter the message log */
QApplication::setOverrideCursor(Qt::WaitCursor);
- ui.listMessages->filter(_filter);
+ ui.listMessages->filter(_logFilter);
QApplication::restoreOverrideCursor();
/* Hide the settings frame and reset toggle button*/
@@ -438,6 +465,18 @@ MessageLog::find()
}
}
+/** Tests if message should be added to the Message History.
+ * \param severity The message's severity type.
+ * \param message The log message to be tested.
+ */
+bool
+MessageLog::testMessage(tc::Severity severity, const QString &message) const
+{
+ if (_logFilter)
+ return _logFilter->eval(severity, message);
+ return true;
+}
+
/** Writes a message to the Message History and tags it with
* the proper date, time and type.
* \param type The message's severity type.
@@ -448,7 +487,7 @@ MessageLog::log(tc::Severity type, const QString &message)
{
setUpdatesEnabled(false);
/* Only add the message if it's not being filtered out */
- if (_filter & (uint)type) {
+ if (testMessage(type, message)) {
/* Add the message to the list and scroll to it if necessary. */
LogTreeItem *item = ui.listMessages->log(type, message);
@@ -477,3 +516,23 @@ MessageLog::help()
emit helpRequested("log");
}
+/** Create regular expression used to filter incoming log messages.
+ * \param input String used to create reg exp.
+ */
+void
+MessageLog::buildMessageFilter(const QString &input)
+{
+ if (_logFilter)
+ delete _logFilter;
+
+ if (input.isEmpty()) {
+ _logFilter = new LogFilter(_filter);
+ } else if (input.startsWith("\\r")) {
+ QString pattern = input;
+ pattern.remove(0,2);
+ const QRegExp rx(pattern.trimmed(), Qt::CaseSensitive, QRegExp::RegExp);
+ _logFilter = new LogFilterRegExp(rx, _filter);
+ } else {
+ _logFilter = new LogFilterSearchTerm(input, _filter);
+ }
+}
diff --git a/src/vidalia/log/MessageLog.h b/src/vidalia/log/MessageLog.h
index 2cb5936..67772e5 100644
--- a/src/vidalia/log/MessageLog.h
+++ b/src/vidalia/log/MessageLog.h
@@ -78,16 +78,22 @@ private:
void save(const QStringList &messages);
/** Rotates the log file based on the filename and the current logging status. */
bool rotateLogFile(const QString &filename);
+ /** Test if given log message should be included into message history. */
+ bool testMessage(tc::Severity severity, const QString& message) const;
+ /** Create filter for log messages. */
+ void buildMessageFilter(const QString& input);
/** A pointer to a TorControl object, used to register for log events */
TorControl* _torControl;
/** A VidaliaSettings object that handles getting/saving settings **/
VidaliaSettings* _settings;
- /** Stores the current message filter */
+ /** Stores the current message severity filter */
uint _filter;
- /** Set to true if we will log all messages to a file. */
- bool _enableLogging;
- /* The log file used to store log messages. */
+ /** Stores message filter */
+ LogFilter* _logFilter;
+ /** Set to true if we will log all messages to a file. */
+ bool _enableLogging;
+ /** The log file used to store log messages. */
LogFile _logFile;
QStatusBar *_statusBar;
diff --git a/src/vidalia/log/MessageLog.ui b/src/vidalia/log/MessageLog.ui
index c72c0ba..029e933 100644
--- a/src/vidalia/log/MessageLog.ui
+++ b/src/vidalia/log/MessageLog.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>534</width>
+ <width>540</width>
<height>521</height>
</rect>
</property>
@@ -155,7 +155,7 @@
</layout>
</item>
<item row="0" column="0" rowspan="2">
- <widget class="QGroupBox" name="groupBox">
+ <widget class="QGroupBox" name="grpLogFilter">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
@@ -251,11 +251,32 @@
</property>
</widget>
</item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QCheckBox" name="chkFilterSearch">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineSearchTerm">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
<item row="0" column="1">
- <widget class="QGroupBox" name="grou">
+ <widget class="QGroupBox" name="grpLogHistory">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
@@ -308,7 +329,7 @@
</widget>
</item>
<item row="1" column="1" colspan="2">
- <widget class="QGroupBox" name="groupBox_2">
+ <widget class="QGroupBox" name="grpSaveNewLog">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits