LCOV - code coverage report
Current view: top level - src/qt - rpcconsole.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 0 410 0.0 %
Date: 2015-10-12 22:39:14 Functions: 0 50 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2011-2014 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include "rpcconsole.h"
       6             : #include "ui_debugwindow.h"
       7             : 
       8             : #include "bantablemodel.h"
       9             : #include "clientmodel.h"
      10             : #include "guiutil.h"
      11             : #include "platformstyle.h"
      12             : #include "bantablemodel.h"
      13             : 
      14             : #include "chainparams.h"
      15             : #include "rpcserver.h"
      16             : #include "rpcclient.h"
      17             : #include "util.h"
      18             : 
      19             : #include <openssl/crypto.h>
      20             : 
      21             : #include <univalue.h>
      22             : 
      23             : #ifdef ENABLE_WALLET
      24             : #include <db_cxx.h>
      25             : #endif
      26             : 
      27             : #include <QKeyEvent>
      28             : #include <QMenu>
      29             : #include <QScrollBar>
      30             : #include <QSignalMapper>
      31             : #include <QThread>
      32             : #include <QTime>
      33             : #include <QTimer>
      34             : 
      35             : #if QT_VERSION < 0x050000
      36             : #include <QUrl>
      37             : #endif
      38             : 
      39             : // TODO: add a scrollback limit, as there is currently none
      40             : // TODO: make it possible to filter out categories (esp debug messages when implemented)
      41             : // TODO: receive errors and debug messages through ClientModel
      42             : 
      43             : const int CONSOLE_HISTORY = 50;
      44             : const QSize ICON_SIZE(24, 24);
      45             : 
      46             : const int INITIAL_TRAFFIC_GRAPH_MINS = 30;
      47             : 
      48             : const struct {
      49             :     const char *url;
      50             :     const char *source;
      51             : } ICON_MAPPING[] = {
      52             :     {"cmd-request", ":/icons/tx_input"},
      53             :     {"cmd-reply", ":/icons/tx_output"},
      54             :     {"cmd-error", ":/icons/tx_output"},
      55             :     {"misc", ":/icons/tx_inout"},
      56             :     {NULL, NULL}
      57             : };
      58             : 
      59             : /* Object for executing console RPC commands in a separate thread.
      60             : */
      61           0 : class RPCExecutor : public QObject
      62             : {
      63             :     Q_OBJECT
      64             : 
      65             : public Q_SLOTS:
      66             :     void request(const QString &command);
      67             : 
      68             : Q_SIGNALS:
      69             :     void reply(int category, const QString &command);
      70             : };
      71             : 
      72             : /** Class for handling RPC timers
      73             :  * (used for e.g. re-locking the wallet after a timeout)
      74             :  */
      75             : class QtRPCTimerBase: public QObject, public RPCTimerBase
      76             : {
      77             :     Q_OBJECT
      78             : public:
      79           0 :     QtRPCTimerBase(boost::function<void(void)>& func, int64_t millis):
      80           0 :         func(func)
      81             :     {
      82           0 :         timer.setSingleShot(true);
      83           0 :         connect(&timer, SIGNAL(timeout()), this, SLOT(timeout()));
      84           0 :         timer.start(millis);
      85           0 :     }
      86           0 :     ~QtRPCTimerBase() {}
      87             : private Q_SLOTS:
      88           0 :     void timeout() { func(); }
      89             : private:
      90             :     QTimer timer;
      91             :     boost::function<void(void)> func;
      92             : };
      93             : 
      94           0 : class QtRPCTimerInterface: public RPCTimerInterface
      95             : {
      96             : public:
      97           0 :     ~QtRPCTimerInterface() {}
      98           0 :     const char *Name() { return "Qt"; }
      99           0 :     RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis)
     100             :     {
     101           0 :         return new QtRPCTimerBase(func, millis);
     102             :     }
     103             : };
     104             : 
     105             : 
     106             : #include "rpcconsole.moc"
     107             : 
     108             : /**
     109             :  * Split shell command line into a list of arguments. Aims to emulate \c bash and friends.
     110             :  *
     111             :  * - Arguments are delimited with whitespace
     112             :  * - Extra whitespace at the beginning and end and between arguments will be ignored
     113             :  * - Text can be "double" or 'single' quoted
     114             :  * - The backslash \c \ is used as escape character
     115             :  *   - Outside quotes, any character can be escaped
     116             :  *   - Within double quotes, only escape \c " and backslashes before a \c " or another backslash
     117             :  *   - Within single quotes, no escaping is possible and no special interpretation takes place
     118             :  *
     119             :  * @param[out]   args        Parsed arguments will be appended to this list
     120             :  * @param[in]    strCommand  Command line to split
     121             :  */
     122           0 : bool parseCommandLine(std::vector<std::string> &args, const std::string &strCommand)
     123             : {
     124             :     enum CmdParseState
     125             :     {
     126             :         STATE_EATING_SPACES,
     127             :         STATE_ARGUMENT,
     128             :         STATE_SINGLEQUOTED,
     129             :         STATE_DOUBLEQUOTED,
     130             :         STATE_ESCAPE_OUTER,
     131             :         STATE_ESCAPE_DOUBLEQUOTED
     132           0 :     } state = STATE_EATING_SPACES;
     133             :     std::string curarg;
     134           0 :     Q_FOREACH(char ch, strCommand)
     135             :     {
     136           0 :         switch(state)
     137             :         {
     138             :         case STATE_ARGUMENT: // In or after argument
     139             :         case STATE_EATING_SPACES: // Handle runs of whitespace
     140           0 :             switch(ch)
     141             :             {
     142             :             case '"': state = STATE_DOUBLEQUOTED; break;
     143           0 :             case '\'': state = STATE_SINGLEQUOTED; break;
     144           0 :             case '\\': state = STATE_ESCAPE_OUTER; break;
     145             :             case ' ': case '\n': case '\t':
     146           0 :                 if(state == STATE_ARGUMENT) // Space ends argument
     147             :                 {
     148           0 :                     args.push_back(curarg);
     149             :                     curarg.clear();
     150             :                 }
     151             :                 state = STATE_EATING_SPACES;
     152             :                 break;
     153           0 :             default: curarg += ch; state = STATE_ARGUMENT;
     154             :             }
     155             :             break;
     156             :         case STATE_SINGLEQUOTED: // Single-quoted string
     157           0 :             switch(ch)
     158             :             {
     159             :             case '\'': state = STATE_ARGUMENT; break;
     160           0 :             default: curarg += ch;
     161             :             }
     162             :             break;
     163             :         case STATE_DOUBLEQUOTED: // Double-quoted string
     164           0 :             switch(ch)
     165             :             {
     166             :             case '"': state = STATE_ARGUMENT; break;
     167           0 :             case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break;
     168           0 :             default: curarg += ch;
     169             :             }
     170             :             break;
     171             :         case STATE_ESCAPE_OUTER: // '\' outside quotes
     172           0 :             curarg += ch; state = STATE_ARGUMENT;
     173             :             break;
     174             :         case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
     175           0 :             if(ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself
     176           0 :             curarg += ch; state = STATE_DOUBLEQUOTED;
     177             :             break;
     178             :         }
     179             :     }
     180           0 :     switch(state) // final state
     181             :     {
     182             :     case STATE_EATING_SPACES:
     183             :         return true;
     184             :     case STATE_ARGUMENT:
     185           0 :         args.push_back(curarg);
     186             :         return true;
     187             :     default: // ERROR to end in one of the other states
     188           0 :         return false;
     189             :     }
     190             : }
     191             : 
     192           0 : void RPCExecutor::request(const QString &command)
     193             : {
     194             :     std::vector<std::string> args;
     195           0 :     if(!parseCommandLine(args, command.toStdString()))
     196             :     {
     197           0 :         Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
     198           0 :         return;
     199             :     }
     200           0 :     if(args.empty())
     201             :         return; // Nothing to do
     202             :     try
     203             :     {
     204             :         std::string strPrint;
     205             :         // Convert argument list to JSON objects in method-dependent way,
     206             :         // and pass it along with the method name to the dispatcher.
     207             :         UniValue result = tableRPC.execute(
     208           0 :             args[0],
     209           0 :             RPCConvertValues(args[0], std::vector<std::string>(args.begin() + 1, args.end())));
     210             : 
     211             :         // Format result reply
     212           0 :         if (result.isNull())
     213             :             strPrint = "";
     214           0 :         else if (result.isStr())
     215           0 :             strPrint = result.get_str();
     216             :         else
     217           0 :             strPrint = result.write(2);
     218             : 
     219           0 :         Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(strPrint));
     220             :     }
     221           0 :     catch (UniValue& objError)
     222             :     {
     223             :         try // Nice formatting for standard-format error
     224             :         {
     225           0 :             int code = find_value(objError, "code").get_int();
     226           0 :             std::string message = find_value(objError, "message").get_str();
     227           0 :             Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")");
     228             :         }
     229           0 :         catch (const std::runtime_error&) // raised when converting to invalid type, i.e. missing code or message
     230             :         {   // Show raw JSON object
     231           0 :             Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(objError.write()));
     232             :         }
     233             :     }
     234           0 :     catch (const std::exception& e)
     235             :     {
     236           0 :         Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what()));
     237           0 :     }
     238             : }
     239             : 
     240           0 : RPCConsole::RPCConsole(const PlatformStyle *platformStyle, QWidget *parent) :
     241             :     QWidget(parent),
     242           0 :     ui(new Ui::RPCConsole),
     243             :     clientModel(0),
     244             :     historyPtr(0),
     245             :     cachedNodeid(-1),
     246             :     platformStyle(platformStyle),
     247             :     peersTableContextMenu(0),
     248           0 :     banTableContextMenu(0)
     249             : {
     250           0 :     ui->setupUi(this);
     251           0 :     GUIUtil::restoreWindowGeometry("nRPCConsoleWindow", this->size(), this);
     252             : 
     253           0 :     if (platformStyle->getImagesOnButtons()) {
     254           0 :         ui->openDebugLogfileButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
     255             :     }
     256           0 :     ui->clearButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
     257             : 
     258             :     // Install event filter for up and down arrow
     259           0 :     ui->lineEdit->installEventFilter(this);
     260           0 :     ui->messagesWidget->installEventFilter(this);
     261             : 
     262           0 :     connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
     263           0 :     connect(ui->btnClearTrafficGraph, SIGNAL(clicked()), ui->trafficGraph, SLOT(clear()));
     264             : 
     265             :     // set library version labels
     266           0 :     ui->openSSLVersion->setText(SSLeay_version(SSLEAY_VERSION));
     267             : #ifdef ENABLE_WALLET
     268           0 :     ui->berkeleyDBVersion->setText(DbEnv::version(0, 0, 0));
     269             : #else
     270             :     ui->label_berkeleyDBVersion->hide();
     271             :     ui->berkeleyDBVersion->hide();
     272             : #endif
     273             :     // Register RPC timer interface
     274           0 :     rpcTimerInterface = new QtRPCTimerInterface();
     275           0 :     RPCRegisterTimerInterface(rpcTimerInterface);
     276             : 
     277           0 :     startExecutor();
     278           0 :     setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
     279             : 
     280           0 :     ui->detailWidget->hide();
     281           0 :     ui->peerHeading->setText(tr("Select a peer to view detailed information."));
     282             : 
     283           0 :     clear();
     284           0 : }
     285             : 
     286           0 : RPCConsole::~RPCConsole()
     287             : {
     288           0 :     GUIUtil::saveWindowGeometry("nRPCConsoleWindow", this);
     289           0 :     Q_EMIT stopExecutor();
     290           0 :     RPCUnregisterTimerInterface(rpcTimerInterface);
     291           0 :     delete rpcTimerInterface;
     292           0 :     delete ui;
     293           0 : }
     294             : 
     295           0 : bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
     296             : {
     297           0 :     if(event->type() == QEvent::KeyPress) // Special key handling
     298             :     {
     299           0 :         QKeyEvent *keyevt = static_cast<QKeyEvent*>(event);
     300           0 :         int key = keyevt->key();
     301           0 :         Qt::KeyboardModifiers mod = keyevt->modifiers();
     302           0 :         switch(key)
     303             :         {
     304           0 :         case Qt::Key_Up: if(obj == ui->lineEdit) { browseHistory(-1); return true; } break;
     305           0 :         case Qt::Key_Down: if(obj == ui->lineEdit) { browseHistory(1); return true; } break;
     306             :         case Qt::Key_PageUp: /* pass paging keys to messages widget */
     307             :         case Qt::Key_PageDown:
     308           0 :             if(obj == ui->lineEdit)
     309             :             {
     310           0 :                 QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt));
     311             :                 return true;
     312             :             }
     313             :             break;
     314             :         default:
     315             :             // Typing in messages widget brings focus to line edit, and redirects key there
     316             :             // Exclude most combinations and keys that emit no text, except paste shortcuts
     317           0 :             if(obj == ui->messagesWidget && (
     318           0 :                   (!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) ||
     319           0 :                   ((mod & Qt::ControlModifier) && key == Qt::Key_V) ||
     320           0 :                   ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert)))
     321             :             {
     322           0 :                 ui->lineEdit->setFocus();
     323           0 :                 QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt));
     324             :                 return true;
     325             :             }
     326             :         }
     327             :     }
     328           0 :     return QWidget::eventFilter(obj, event);
     329             : }
     330             : 
     331           0 : void RPCConsole::setClientModel(ClientModel *model)
     332             : {
     333           0 :     clientModel = model;
     334           0 :     ui->trafficGraph->setClientModel(model);
     335           0 :     if (model && clientModel->getPeerTableModel() && clientModel->getBanTableModel()) {
     336             :         // Keep up to date with client
     337           0 :         setNumConnections(model->getNumConnections());
     338           0 :         connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
     339             : 
     340           0 :         setNumBlocks(model->getNumBlocks(), model->getLastBlockDate());
     341           0 :         connect(model, SIGNAL(numBlocksChanged(int,QDateTime)), this, SLOT(setNumBlocks(int,QDateTime)));
     342             : 
     343           0 :         updateTrafficStats(model->getTotalBytesRecv(), model->getTotalBytesSent());
     344           0 :         connect(model, SIGNAL(bytesChanged(quint64,quint64)), this, SLOT(updateTrafficStats(quint64, quint64)));
     345             : 
     346             :         // set up peer table
     347           0 :         ui->peerWidget->setModel(model->getPeerTableModel());
     348           0 :         ui->peerWidget->verticalHeader()->hide();
     349           0 :         ui->peerWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
     350           0 :         ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
     351           0 :         ui->peerWidget->setSelectionMode(QAbstractItemView::SingleSelection);
     352           0 :         ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu);
     353           0 :         ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH);
     354           0 :         ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH);
     355           0 :         ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH);
     356           0 :         ui->peerWidget->horizontalHeader()->setStretchLastSection(true);
     357             : 
     358             :         // create peer table context menu actions
     359           0 :         QAction* disconnectAction = new QAction(tr("&Disconnect Node"), this);
     360           0 :         QAction* banAction1h      = new QAction(tr("Ban Node for") + " " + tr("1 &hour"), this);
     361           0 :         QAction* banAction24h     = new QAction(tr("Ban Node for") + " " + tr("1 &day"), this);
     362           0 :         QAction* banAction7d      = new QAction(tr("Ban Node for") + " " + tr("1 &week"), this);
     363           0 :         QAction* banAction365d    = new QAction(tr("Ban Node for") + " " + tr("1 &year"), this);
     364             : 
     365             :         // create peer table context menu
     366           0 :         peersTableContextMenu = new QMenu();
     367           0 :         peersTableContextMenu->addAction(disconnectAction);
     368           0 :         peersTableContextMenu->addAction(banAction1h);
     369           0 :         peersTableContextMenu->addAction(banAction24h);
     370           0 :         peersTableContextMenu->addAction(banAction7d);
     371           0 :         peersTableContextMenu->addAction(banAction365d);
     372             : 
     373             :         // Add a signal mapping to allow dynamic context menu arguments.
     374             :         // We need to use int (instead of int64_t), because signal mapper only supports
     375             :         // int or objects, which is okay because max bantime (1 year) is < int_max.
     376           0 :         QSignalMapper* signalMapper = new QSignalMapper(this);
     377           0 :         signalMapper->setMapping(banAction1h, 60*60);
     378           0 :         signalMapper->setMapping(banAction24h, 60*60*24);
     379           0 :         signalMapper->setMapping(banAction7d, 60*60*24*7);
     380           0 :         signalMapper->setMapping(banAction365d, 60*60*24*365);
     381           0 :         connect(banAction1h, SIGNAL(triggered()), signalMapper, SLOT(map()));
     382           0 :         connect(banAction24h, SIGNAL(triggered()), signalMapper, SLOT(map()));
     383           0 :         connect(banAction7d, SIGNAL(triggered()), signalMapper, SLOT(map()));
     384           0 :         connect(banAction365d, SIGNAL(triggered()), signalMapper, SLOT(map()));
     385           0 :         connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(banSelectedNode(int)));
     386             : 
     387             :         // peer table context menu signals
     388           0 :         connect(ui->peerWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showPeersTableContextMenu(const QPoint&)));
     389           0 :         connect(disconnectAction, SIGNAL(triggered()), this, SLOT(disconnectSelectedNode()));
     390             : 
     391             :         // peer table signal handling - update peer details when selecting new node
     392           0 :         connect(ui->peerWidget->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
     393           0 :             this, SLOT(peerSelected(const QItemSelection &, const QItemSelection &)));
     394             :         // peer table signal handling - update peer details when new nodes are added to the model
     395           0 :         connect(model->getPeerTableModel(), SIGNAL(layoutChanged()), this, SLOT(peerLayoutChanged()));
     396             : 
     397             :         // set up ban table
     398           0 :         ui->banlistWidget->setModel(model->getBanTableModel());
     399           0 :         ui->banlistWidget->verticalHeader()->hide();
     400           0 :         ui->banlistWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
     401           0 :         ui->banlistWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
     402           0 :         ui->banlistWidget->setSelectionMode(QAbstractItemView::SingleSelection);
     403           0 :         ui->banlistWidget->setContextMenuPolicy(Qt::CustomContextMenu);
     404           0 :         ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH);
     405           0 :         ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH);
     406           0 :         ui->banlistWidget->horizontalHeader()->setStretchLastSection(true);
     407             : 
     408             :         // create ban table context menu action
     409           0 :         QAction* unbanAction = new QAction(tr("&Unban Node"), this);
     410             : 
     411             :         // create ban table context menu
     412           0 :         banTableContextMenu = new QMenu();
     413           0 :         banTableContextMenu->addAction(unbanAction);
     414             : 
     415             :         // ban table context menu signals
     416           0 :         connect(ui->banlistWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showBanTableContextMenu(const QPoint&)));
     417           0 :         connect(unbanAction, SIGNAL(triggered()), this, SLOT(unbanSelectedNode()));
     418             : 
     419             :         // ban table signal handling - clear peer details when clicking a peer in the ban table
     420           0 :         connect(ui->banlistWidget, SIGNAL(clicked(const QModelIndex&)), this, SLOT(clearSelectedNode()));
     421             :         // ban table signal handling - ensure ban table is shown or hidden (if empty)
     422           0 :         connect(model->getBanTableModel(), SIGNAL(layoutChanged()), this, SLOT(showOrHideBanTableIfRequired()));
     423           0 :         showOrHideBanTableIfRequired();
     424             : 
     425             :         // Provide initial values
     426           0 :         ui->clientVersion->setText(model->formatFullVersion());
     427           0 :         ui->clientUserAgent->setText(model->formatSubVersion());
     428           0 :         ui->clientName->setText(model->clientName());
     429           0 :         ui->buildDate->setText(model->formatBuildDate());
     430           0 :         ui->startupTime->setText(model->formatClientStartupTime());
     431           0 :         ui->networkName->setText(QString::fromStdString(Params().NetworkIDString()));
     432             :     }
     433           0 : }
     434             : 
     435           0 : static QString categoryClass(int category)
     436             : {
     437           0 :     switch(category)
     438             :     {
     439             :     case RPCConsole::CMD_REQUEST:  return "cmd-request"; break;
     440             :     case RPCConsole::CMD_REPLY:    return "cmd-reply"; break;
     441             :     case RPCConsole::CMD_ERROR:    return "cmd-error"; break;
     442             :     default:                       return "misc";
     443             :     }
     444             : }
     445             : 
     446           0 : void RPCConsole::clear()
     447             : {
     448           0 :     ui->messagesWidget->clear();
     449           0 :     history.clear();
     450           0 :     historyPtr = 0;
     451           0 :     ui->lineEdit->clear();
     452           0 :     ui->lineEdit->setFocus();
     453             : 
     454             :     // Add smoothly scaled icon images.
     455             :     // (when using width/height on an img, Qt uses nearest instead of linear interpolation)
     456           0 :     for(int i=0; ICON_MAPPING[i].url; ++i)
     457             :     {
     458             :         ui->messagesWidget->document()->addResource(
     459             :                     QTextDocument::ImageResource,
     460             :                     QUrl(ICON_MAPPING[i].url),
     461           0 :                     platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(ICON_SIZE, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
     462             :     }
     463             : 
     464             :     // Set default style sheet
     465             :     ui->messagesWidget->document()->setDefaultStyleSheet(
     466             :                 "table { }"
     467             :                 "td.time { color: #808080; padding-top: 3px; } "
     468             :                 "td.cmd-request { color: #006060; } "
     469             :                 "td.cmd-error { color: red; } "
     470             :                 "b { color: #006060; } "
     471           0 :                 );
     472             : 
     473           0 :     message(CMD_REPLY, (tr("Welcome to the Bitcoin Core RPC console.") + "<br>" +
     474           0 :                         tr("Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen.") + "<br>" +
     475           0 :                         tr("Type <b>help</b> for an overview of available commands.")), true);
     476           0 : }
     477             : 
     478           0 : void RPCConsole::keyPressEvent(QKeyEvent *event)
     479             : {
     480           0 :     if(windowType() != Qt::Widget && event->key() == Qt::Key_Escape)
     481             :     {
     482           0 :         close();
     483             :     }
     484           0 : }
     485             : 
     486           0 : void RPCConsole::message(int category, const QString &message, bool html)
     487             : {
     488           0 :     QTime time = QTime::currentTime();
     489           0 :     QString timeString = time.toString();
     490           0 :     QString out;
     491           0 :     out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
     492           0 :     out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
     493           0 :     out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
     494           0 :     if(html)
     495             :         out += message;
     496             :     else
     497           0 :         out += GUIUtil::HtmlEscape(message, true);
     498           0 :     out += "</td></tr></table>";
     499           0 :     ui->messagesWidget->append(out);
     500           0 : }
     501             : 
     502           0 : void RPCConsole::setNumConnections(int count)
     503             : {
     504           0 :     if (!clientModel)
     505           0 :         return;
     506             : 
     507           0 :     QString connections = QString::number(count) + " (";
     508           0 :     connections += tr("In:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_IN)) + " / ";
     509           0 :     connections += tr("Out:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_OUT)) + ")";
     510             : 
     511           0 :     ui->numberOfConnections->setText(connections);
     512             : }
     513             : 
     514           0 : void RPCConsole::setNumBlocks(int count, const QDateTime& blockDate)
     515             : {
     516           0 :     ui->numberOfBlocks->setText(QString::number(count));
     517           0 :     ui->lastBlockTime->setText(blockDate.toString());
     518           0 : }
     519             : 
     520           0 : void RPCConsole::on_lineEdit_returnPressed()
     521             : {
     522           0 :     QString cmd = ui->lineEdit->text();
     523           0 :     ui->lineEdit->clear();
     524             : 
     525           0 :     if(!cmd.isEmpty())
     526             :     {
     527           0 :         message(CMD_REQUEST, cmd);
     528           0 :         Q_EMIT cmdRequest(cmd);
     529             :         // Remove command, if already in history
     530           0 :         history.removeOne(cmd);
     531             :         // Append command to history
     532           0 :         history.append(cmd);
     533             :         // Enforce maximum history size
     534           0 :         while(history.size() > CONSOLE_HISTORY)
     535           0 :             history.removeFirst();
     536             :         // Set pointer to end of history
     537           0 :         historyPtr = history.size();
     538             :         // Scroll console view to end
     539           0 :         scrollToEnd();
     540           0 :     }
     541           0 : }
     542             : 
     543           0 : void RPCConsole::browseHistory(int offset)
     544             : {
     545           0 :     historyPtr += offset;
     546           0 :     if(historyPtr < 0)
     547           0 :         historyPtr = 0;
     548           0 :     if(historyPtr > history.size())
     549           0 :         historyPtr = history.size();
     550             :     QString cmd;
     551           0 :     if(historyPtr < history.size())
     552           0 :         cmd = history.at(historyPtr);
     553           0 :     ui->lineEdit->setText(cmd);
     554           0 : }
     555             : 
     556           0 : void RPCConsole::startExecutor()
     557             : {
     558           0 :     QThread *thread = new QThread;
     559           0 :     RPCExecutor *executor = new RPCExecutor();
     560           0 :     executor->moveToThread(thread);
     561             : 
     562             :     // Replies from executor object must go to this object
     563           0 :     connect(executor, SIGNAL(reply(int,QString)), this, SLOT(message(int,QString)));
     564             :     // Requests from this object must go to executor
     565           0 :     connect(this, SIGNAL(cmdRequest(QString)), executor, SLOT(request(QString)));
     566             : 
     567             :     // On stopExecutor signal
     568             :     // - queue executor for deletion (in execution thread)
     569             :     // - quit the Qt event loop in the execution thread
     570           0 :     connect(this, SIGNAL(stopExecutor()), executor, SLOT(deleteLater()));
     571           0 :     connect(this, SIGNAL(stopExecutor()), thread, SLOT(quit()));
     572             :     // Queue the thread for deletion (in this thread) when it is finished
     573           0 :     connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
     574             : 
     575             :     // Default implementation of QThread::run() simply spins up an event loop in the thread,
     576             :     // which is what we want.
     577           0 :     thread->start();
     578           0 : }
     579             : 
     580           0 : void RPCConsole::on_tabWidget_currentChanged(int index)
     581             : {
     582           0 :     if (ui->tabWidget->widget(index) == ui->tab_console)
     583           0 :         ui->lineEdit->setFocus();
     584           0 :     else if (ui->tabWidget->widget(index) != ui->tab_peers)
     585           0 :         clearSelectedNode();
     586           0 : }
     587             : 
     588           0 : void RPCConsole::on_openDebugLogfileButton_clicked()
     589             : {
     590           0 :     GUIUtil::openDebugLogfile();
     591           0 : }
     592             : 
     593           0 : void RPCConsole::scrollToEnd()
     594             : {
     595           0 :     QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar();
     596           0 :     scrollbar->setValue(scrollbar->maximum());
     597           0 : }
     598             : 
     599           0 : void RPCConsole::on_sldGraphRange_valueChanged(int value)
     600             : {
     601           0 :     const int multiplier = 5; // each position on the slider represents 5 min
     602           0 :     int mins = value * multiplier;
     603           0 :     setTrafficGraphRange(mins);
     604           0 : }
     605             : 
     606           0 : QString RPCConsole::FormatBytes(quint64 bytes)
     607             : {
     608           0 :     if(bytes < 1024)
     609           0 :         return QString(tr("%1 B")).arg(bytes);
     610           0 :     if(bytes < 1024 * 1024)
     611           0 :         return QString(tr("%1 KB")).arg(bytes / 1024);
     612           0 :     if(bytes < 1024 * 1024 * 1024)
     613           0 :         return QString(tr("%1 MB")).arg(bytes / 1024 / 1024);
     614             : 
     615           0 :     return QString(tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024);
     616             : }
     617             : 
     618           0 : void RPCConsole::setTrafficGraphRange(int mins)
     619             : {
     620           0 :     ui->trafficGraph->setGraphRangeMins(mins);
     621           0 :     ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60));
     622           0 : }
     623             : 
     624           0 : void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut)
     625             : {
     626           0 :     ui->lblBytesIn->setText(FormatBytes(totalBytesIn));
     627           0 :     ui->lblBytesOut->setText(FormatBytes(totalBytesOut));
     628           0 : }
     629             : 
     630           0 : void RPCConsole::peerSelected(const QItemSelection &selected, const QItemSelection &deselected)
     631             : {
     632             :     Q_UNUSED(deselected);
     633             : 
     634           0 :     if (!clientModel || !clientModel->getPeerTableModel() || selected.indexes().isEmpty())
     635           0 :         return;
     636             : 
     637           0 :     const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(selected.indexes().first().row());
     638           0 :     if (stats)
     639           0 :         updateNodeDetail(stats);
     640             : }
     641             : 
     642           0 : void RPCConsole::peerLayoutChanged()
     643             : {
     644           0 :     if (!clientModel || !clientModel->getPeerTableModel())
     645           0 :         return;
     646             : 
     647           0 :     const CNodeCombinedStats *stats = NULL;
     648           0 :     bool fUnselect = false;
     649           0 :     bool fReselect = false;
     650             : 
     651           0 :     if (cachedNodeid == -1) // no node selected yet
     652             :         return;
     653             : 
     654             :     // find the currently selected row
     655           0 :     int selectedRow = -1;
     656           0 :     QModelIndexList selectedModelIndex = ui->peerWidget->selectionModel()->selectedIndexes();
     657           0 :     if (!selectedModelIndex.isEmpty()) {
     658           0 :         selectedRow = selectedModelIndex.first().row();
     659             :     }
     660             : 
     661             :     // check if our detail node has a row in the table (it may not necessarily
     662             :     // be at selectedRow since its position can change after a layout change)
     663           0 :     int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeid);
     664             : 
     665           0 :     if (detailNodeRow < 0)
     666             :     {
     667             :         // detail node disappeared from table (node disconnected)
     668             :         fUnselect = true;
     669             :     }
     670             :     else
     671             :     {
     672           0 :         if (detailNodeRow != selectedRow)
     673             :         {
     674             :             // detail node moved position
     675           0 :             fUnselect = true;
     676           0 :             fReselect = true;
     677             :         }
     678             : 
     679             :         // get fresh stats on the detail node.
     680           0 :         stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow);
     681             :     }
     682             : 
     683           0 :     if (fUnselect && selectedRow >= 0) {
     684           0 :         clearSelectedNode();
     685             :     }
     686             : 
     687           0 :     if (fReselect)
     688             :     {
     689           0 :         ui->peerWidget->selectRow(detailNodeRow);
     690             :     }
     691             : 
     692           0 :     if (stats)
     693           0 :         updateNodeDetail(stats);
     694             : }
     695             : 
     696           0 : void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats)
     697             : {
     698             :     // Update cached nodeid
     699           0 :     cachedNodeid = stats->nodeStats.nodeid;
     700             : 
     701             :     // update the detail ui with latest node information
     702           0 :     QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) + " ");
     703           0 :     peerAddrDetails += tr("(node id: %1)").arg(QString::number(stats->nodeStats.nodeid));
     704           0 :     if (!stats->nodeStats.addrLocal.empty())
     705           0 :         peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal));
     706           0 :     ui->peerHeading->setText(peerAddrDetails);
     707           0 :     ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStats.nServices));
     708           0 :     ui->peerLastSend->setText(stats->nodeStats.nLastSend ? GUIUtil::formatDurationStr(GetTime() - stats->nodeStats.nLastSend) : tr("never"));
     709           0 :     ui->peerLastRecv->setText(stats->nodeStats.nLastRecv ? GUIUtil::formatDurationStr(GetTime() - stats->nodeStats.nLastRecv) : tr("never"));
     710           0 :     ui->peerBytesSent->setText(FormatBytes(stats->nodeStats.nSendBytes));
     711           0 :     ui->peerBytesRecv->setText(FormatBytes(stats->nodeStats.nRecvBytes));
     712           0 :     ui->peerConnTime->setText(GUIUtil::formatDurationStr(GetTime() - stats->nodeStats.nTimeConnected));
     713           0 :     ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.dPingTime));
     714           0 :     ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStats.dPingWait));
     715           0 :     ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
     716           0 :     ui->peerVersion->setText(QString("%1").arg(QString::number(stats->nodeStats.nVersion)));
     717           0 :     ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
     718           0 :     ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound"));
     719           0 :     ui->peerHeight->setText(QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight)));
     720           0 :     ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes") : tr("No"));
     721             : 
     722             :     // This check fails for example if the lock was busy and
     723             :     // nodeStateStats couldn't be fetched.
     724           0 :     if (stats->fNodeStateStatsAvailable) {
     725             :         // Ban score is init to 0
     726           0 :         ui->peerBanScore->setText(QString("%1").arg(stats->nodeStateStats.nMisbehavior));
     727             : 
     728             :         // Sync height is init to -1
     729           0 :         if (stats->nodeStateStats.nSyncHeight > -1)
     730           0 :             ui->peerSyncHeight->setText(QString("%1").arg(stats->nodeStateStats.nSyncHeight));
     731             :         else
     732           0 :             ui->peerSyncHeight->setText(tr("Unknown"));
     733             : 
     734             :         // Common height is init to -1
     735           0 :         if (stats->nodeStateStats.nCommonHeight > -1)
     736           0 :             ui->peerCommonHeight->setText(QString("%1").arg(stats->nodeStateStats.nCommonHeight));
     737             :         else
     738           0 :             ui->peerCommonHeight->setText(tr("Unknown"));
     739             :     }
     740             : 
     741           0 :     ui->detailWidget->show();
     742           0 : }
     743             : 
     744           0 : void RPCConsole::resizeEvent(QResizeEvent *event)
     745             : {
     746           0 :     QWidget::resizeEvent(event);
     747           0 : }
     748             : 
     749           0 : void RPCConsole::showEvent(QShowEvent *event)
     750             : {
     751           0 :     QWidget::showEvent(event);
     752             : 
     753           0 :     if (!clientModel || !clientModel->getPeerTableModel())
     754           0 :         return;
     755             : 
     756             :     // start PeerTableModel auto refresh
     757           0 :     clientModel->getPeerTableModel()->startAutoRefresh();
     758             : }
     759             : 
     760           0 : void RPCConsole::hideEvent(QHideEvent *event)
     761             : {
     762           0 :     QWidget::hideEvent(event);
     763             : 
     764           0 :     if (!clientModel || !clientModel->getPeerTableModel())
     765           0 :         return;
     766             : 
     767             :     // stop PeerTableModel auto refresh
     768           0 :     clientModel->getPeerTableModel()->stopAutoRefresh();
     769             : }
     770             : 
     771           0 : void RPCConsole::showPeersTableContextMenu(const QPoint& point)
     772             : {
     773           0 :     QModelIndex index = ui->peerWidget->indexAt(point);
     774           0 :     if (index.isValid())
     775           0 :         peersTableContextMenu->exec(QCursor::pos());
     776           0 : }
     777             : 
     778           0 : void RPCConsole::showBanTableContextMenu(const QPoint& point)
     779             : {
     780           0 :     QModelIndex index = ui->banlistWidget->indexAt(point);
     781           0 :     if (index.isValid())
     782           0 :         banTableContextMenu->exec(QCursor::pos());
     783           0 : }
     784             : 
     785           0 : void RPCConsole::disconnectSelectedNode()
     786             : {
     787             :     // Get currently selected peer address
     788           0 :     QString strNode = GUIUtil::getEntryData(ui->peerWidget, 0, PeerTableModel::Address);
     789             :     // Find the node, disconnect it and clear the selected node
     790           0 :     if (CNode *bannedNode = FindNode(strNode.toStdString())) {
     791           0 :         bannedNode->fDisconnect = true;
     792           0 :         clearSelectedNode();
     793           0 :     }
     794           0 : }
     795             : 
     796           0 : void RPCConsole::banSelectedNode(int bantime)
     797             : {
     798           0 :     if (!clientModel)
     799           0 :         return;
     800             : 
     801             :     // Get currently selected peer address
     802           0 :     QString strNode = GUIUtil::getEntryData(ui->peerWidget, 0, PeerTableModel::Address);
     803             :     // Find possible nodes, ban it and clear the selected node
     804           0 :     if (CNode *bannedNode = FindNode(strNode.toStdString())) {
     805           0 :         std::string nStr = strNode.toStdString();
     806             :         std::string addr;
     807           0 :         int port = 0;
     808           0 :         SplitHostPort(nStr, port, addr);
     809             : 
     810           0 :         CNode::Ban(CNetAddr(addr), BanReasonManuallyAdded, bantime);
     811           0 :         bannedNode->fDisconnect = true;
     812           0 :         DumpBanlist();
     813             : 
     814           0 :         clearSelectedNode();
     815           0 :         clientModel->getBanTableModel()->refresh();
     816           0 :     }
     817             : }
     818             : 
     819           0 : void RPCConsole::unbanSelectedNode()
     820             : {
     821           0 :     if (!clientModel)
     822           0 :         return;
     823             : 
     824             :     // Get currently selected ban address
     825           0 :     QString strNode = GUIUtil::getEntryData(ui->banlistWidget, 0, BanTableModel::Address);
     826           0 :     CSubNet possibleSubnet(strNode.toStdString());
     827             : 
     828           0 :     if (possibleSubnet.IsValid())
     829             :     {
     830           0 :         CNode::Unban(possibleSubnet);
     831           0 :         DumpBanlist();
     832           0 :         clientModel->getBanTableModel()->refresh();
     833           0 :     }
     834             : }
     835             : 
     836           0 : void RPCConsole::clearSelectedNode()
     837             : {
     838           0 :     ui->peerWidget->selectionModel()->clearSelection();
     839           0 :     cachedNodeid = -1;
     840           0 :     ui->detailWidget->hide();
     841           0 :     ui->peerHeading->setText(tr("Select a peer to view detailed information."));
     842           0 : }
     843             : 
     844           0 : void RPCConsole::showOrHideBanTableIfRequired()
     845             : {
     846           0 :     if (!clientModel)
     847           0 :         return;
     848             : 
     849           0 :     bool visible = clientModel->getBanTableModel()->shouldShow();
     850           0 :     ui->banlistWidget->setVisible(visible);
     851           0 :     ui->banHeading->setVisible(visible);
     852           0 : }

Generated by: LCOV version 1.11