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

chiiph at torproject.org chiiph at torproject.org
Sat Mar 24 16:22:02 UTC 2012


commit 69f9bd84027043bfd87f749d4e61ccd5bdfdb1c1
Author: Sebastian Baginski <sebthestampede at 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>





More information about the tor-commits mailing list