[vidalia/alpha] option to filter message log by search term

commit 69f9bd84027043bfd87f749d4e61ccd5bdfdb1c1 Author: Sebastian Baginski <sebthestampede@gmail.com> 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>
participants (1)
-
chiiph@torproject.org