Feathercoin  0.5.0
P2P Digital Currency
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
rpcconsole.cpp
Go to the documentation of this file.
1 #include "rpcconsole.h"
2 #include "ui_rpcconsole.h"
3 
4 #include "clientmodel.h"
5 #include "bitcoinrpc.h"
6 #include "guiutil.h"
7 
8 #include <QTime>
9 #include <QThread>
10 #include <QKeyEvent>
11 #if QT_VERSION < 0x050000
12 #include <QUrl>
13 #endif
14 #include <QScrollBar>
15 
16 #include <openssl/crypto.h>
17 
18 // TODO: add a scrollback limit, as there is currently none
19 // TODO: make it possible to filter out categories (esp debug messages when implemented)
20 // TODO: receive errors and debug messages through ClientModel
21 
22 const int CONSOLE_HISTORY = 50;
23 const QSize ICON_SIZE(24, 24);
24 
25 const struct {
26  const char *url;
27  const char *source;
28 } ICON_MAPPING[] = {
29  {"cmd-request", ":/icons/tx_input"},
30  {"cmd-reply", ":/icons/tx_output"},
31  {"cmd-error", ":/icons/tx_output"},
32  {"misc", ":/icons/tx_inout"},
33  {NULL, NULL}
34 };
35 
36 /* Object for executing console RPC commands in a separate thread.
37 */
38 class RPCExecutor : public QObject
39 {
40  Q_OBJECT
41 
42 public slots:
43  void request(const QString &command);
44 
45 signals:
46  void reply(int category, const QString &command);
47 };
48 
49 #include "rpcconsole.moc"
50 
65 bool parseCommandLine(std::vector<std::string> &args, const std::string &strCommand)
66 {
67  enum CmdParseState
68  {
69  STATE_EATING_SPACES,
70  STATE_ARGUMENT,
71  STATE_SINGLEQUOTED,
72  STATE_DOUBLEQUOTED,
73  STATE_ESCAPE_OUTER,
74  STATE_ESCAPE_DOUBLEQUOTED
75  } state = STATE_EATING_SPACES;
76  std::string curarg;
77  foreach(char ch, strCommand)
78  {
79  switch(state)
80  {
81  case STATE_ARGUMENT: // In or after argument
82  case STATE_EATING_SPACES: // Handle runs of whitespace
83  switch(ch)
84  {
85  case '"': state = STATE_DOUBLEQUOTED; break;
86  case '\'': state = STATE_SINGLEQUOTED; break;
87  case '\\': state = STATE_ESCAPE_OUTER; break;
88  case ' ': case '\n': case '\t':
89  if(state == STATE_ARGUMENT) // Space ends argument
90  {
91  args.push_back(curarg);
92  curarg.clear();
93  }
94  state = STATE_EATING_SPACES;
95  break;
96  default: curarg += ch; state = STATE_ARGUMENT;
97  }
98  break;
99  case STATE_SINGLEQUOTED: // Single-quoted string
100  switch(ch)
101  {
102  case '\'': state = STATE_ARGUMENT; break;
103  default: curarg += ch;
104  }
105  break;
106  case STATE_DOUBLEQUOTED: // Double-quoted string
107  switch(ch)
108  {
109  case '"': state = STATE_ARGUMENT; break;
110  case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break;
111  default: curarg += ch;
112  }
113  break;
114  case STATE_ESCAPE_OUTER: // '\' outside quotes
115  curarg += ch; state = STATE_ARGUMENT;
116  break;
117  case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
118  if(ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself
119  curarg += ch; state = STATE_DOUBLEQUOTED;
120  break;
121  }
122  }
123  switch(state) // final state
124  {
125  case STATE_EATING_SPACES:
126  return true;
127  case STATE_ARGUMENT:
128  args.push_back(curarg);
129  return true;
130  default: // ERROR to end in one of the other states
131  return false;
132  }
133 }
134 
135 void RPCExecutor::request(const QString &command)
136 {
137  std::vector<std::string> args;
138  if(!parseCommandLine(args, command.toStdString()))
139  {
140  emit reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
141  return;
142  }
143  if(args.empty())
144  return; // Nothing to do
145  try
146  {
147  std::string strPrint;
148  // Convert argument list to JSON objects in method-dependent way,
149  // and pass it along with the method name to the dispatcher.
151  args[0],
152  RPCConvertValues(args[0], std::vector<std::string>(args.begin() + 1, args.end())));
153 
154  // Format result reply
155  if (result.type() == json_spirit::null_type)
156  strPrint = "";
157  else if (result.type() == json_spirit::str_type)
158  strPrint = result.get_str();
159  else
160  strPrint = write_string(result, true);
161 
162  emit reply(RPCConsole::CMD_REPLY, QString::fromStdString(strPrint));
163  }
164  catch (json_spirit::Object& objError)
165  {
166  try // Nice formatting for standard-format error
167  {
168  int code = find_value(objError, "code").get_int();
169  std::string message = find_value(objError, "message").get_str();
170  emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")");
171  }
172  catch(std::runtime_error &) // raised when converting to invalid type, i.e. missing code or message
173  { // Show raw JSON object
174  emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(write_string(json_spirit::Value(objError), false)));
175  }
176  }
177  catch (std::exception& e)
178  {
179  emit reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what()));
180  }
181 }
182 
183 RPCConsole::RPCConsole(QWidget *parent) :
184  QDialog(parent),
185  ui(new Ui::RPCConsole),
186  clientModel(0),
187  historyPtr(0)
188 {
189  ui->setupUi(this);
190 
191 #ifndef Q_OS_MAC
192  ui->openDebugLogfileButton->setIcon(QIcon(":/icons/export"));
193  ui->showCLOptionsButton->setIcon(QIcon(":/icons/options"));
194 #endif
195 
196  // Install event filter for up and down arrow
197  ui->lineEdit->installEventFilter(this);
198  ui->messagesWidget->installEventFilter(this);
199 
200  connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
201 
202  // set OpenSSL version label
203  ui->openSSLVersion->setText(SSLeay_version(SSLEAY_VERSION));
204 
205  startExecutor();
206 
207  clear();
208 }
209 
211 {
212  emit stopExecutor();
213  delete ui;
214 }
215 
216 bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
217 {
218  if(event->type() == QEvent::KeyPress) // Special key handling
219  {
220  QKeyEvent *keyevt = static_cast<QKeyEvent*>(event);
221  int key = keyevt->key();
222  Qt::KeyboardModifiers mod = keyevt->modifiers();
223  switch(key)
224  {
225  case Qt::Key_Up: if(obj == ui->lineEdit) { browseHistory(-1); return true; } break;
226  case Qt::Key_Down: if(obj == ui->lineEdit) { browseHistory(1); return true; } break;
227  case Qt::Key_PageUp: /* pass paging keys to messages widget */
228  case Qt::Key_PageDown:
229  if(obj == ui->lineEdit)
230  {
231  QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt));
232  return true;
233  }
234  break;
235  default:
236  // Typing in messages widget brings focus to line edit, and redirects key there
237  // Exclude most combinations and keys that emit no text, except paste shortcuts
238  if(obj == ui->messagesWidget && (
239  (!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) ||
240  ((mod & Qt::ControlModifier) && key == Qt::Key_V) ||
241  ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert)))
242  {
243  ui->lineEdit->setFocus();
244  QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt));
245  return true;
246  }
247  }
248  }
249  return QDialog::eventFilter(obj, event);
250 }
251 
253 {
254  this->clientModel = model;
255  if(model)
256  {
257  // Subscribe to information, replies, messages, errors
258  connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
259  connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int)));
260 
261  // Provide initial values
262  ui->clientVersion->setText(model->formatFullVersion());
263  ui->clientName->setText(model->clientName());
264  ui->buildDate->setText(model->formatBuildDate());
265  ui->startupTime->setText(model->formatClientStartupTime());
266 
268  ui->isTestNet->setChecked(model->isTestNet());
269  }
270 }
271 
272 static QString categoryClass(int category)
273 {
274  switch(category)
275  {
276  case RPCConsole::CMD_REQUEST: return "cmd-request"; break;
277  case RPCConsole::CMD_REPLY: return "cmd-reply"; break;
278  case RPCConsole::CMD_ERROR: return "cmd-error"; break;
279  default: return "misc";
280  }
281 }
282 
284 {
285  ui->messagesWidget->clear();
286  history.clear();
287  historyPtr = 0;
288  ui->lineEdit->clear();
289  ui->lineEdit->setFocus();
290 
291  // Add smoothly scaled icon images.
292  // (when using width/height on an img, Qt uses nearest instead of linear interpolation)
293  for(int i=0; ICON_MAPPING[i].url; ++i)
294  {
295  ui->messagesWidget->document()->addResource(
296  QTextDocument::ImageResource,
297  QUrl(ICON_MAPPING[i].url),
298  QImage(ICON_MAPPING[i].source).scaled(ICON_SIZE, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
299  }
300 
301  // Set default style sheet
302  ui->messagesWidget->document()->setDefaultStyleSheet(
303  "table { }"
304  "td.time { color: #808080; padding-top: 3px; } "
305  "td.message { font-family: Monospace; font-size: 12px; } "
306  "td.cmd-request { color: #006060; } "
307  "td.cmd-error { color: red; } "
308  "b { color: #006060; } "
309  );
310 
311  message(CMD_REPLY, (tr("Welcome to the Feathercoin RPC console.") + "<br>" +
312  tr("Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen.") + "<br>" +
313  tr("Type <b>help</b> for an overview of available commands.")), true);
314 }
315 
316 void RPCConsole::message(int category, const QString &message, bool html)
317 {
318  QTime time = QTime::currentTime();
319  QString timeString = time.toString();
320  QString out;
321  out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
322  out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
323  out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
324  if(html)
325  out += message;
326  else
327  out += GUIUtil::HtmlEscape(message, true);
328  out += "</td></tr></table>";
329  ui->messagesWidget->append(out);
330 }
331 
333 {
334  ui->numberOfConnections->setText(QString::number(count));
335 }
336 
337 void RPCConsole::setNumBlocks(int count, int countOfPeers)
338 {
339  ui->numberOfBlocks->setText(QString::number(count));
340  // If there is no current countOfPeers available display N/A instead of 0, which can't ever be true
341  ui->totalBlocks->setText(countOfPeers == 0 ? tr("N/A") : QString::number(countOfPeers));
342  if(clientModel)
343  ui->lastBlockTime->setText(clientModel->getLastBlockDate().toString());
344 }
345 
347 {
348  QString cmd = ui->lineEdit->text();
349  ui->lineEdit->clear();
350 
351  if(!cmd.isEmpty())
352  {
353  message(CMD_REQUEST, cmd);
354  emit cmdRequest(cmd);
355  // Truncate history from current position
356  history.erase(history.begin() + historyPtr, history.end());
357  // Append command to history
358  history.append(cmd);
359  // Enforce maximum history size
360  while(history.size() > CONSOLE_HISTORY)
361  history.removeFirst();
362  // Set pointer to end of history
363  historyPtr = history.size();
364  // Scroll console view to end
365  scrollToEnd();
366  }
367 }
368 
370 {
371  historyPtr += offset;
372  if(historyPtr < 0)
373  historyPtr = 0;
374  if(historyPtr > history.size())
375  historyPtr = history.size();
376  QString cmd;
377  if(historyPtr < history.size())
378  cmd = history.at(historyPtr);
379  ui->lineEdit->setText(cmd);
380 }
381 
383 {
384  QThread *thread = new QThread;
385  RPCExecutor *executor = new RPCExecutor();
386  executor->moveToThread(thread);
387 
388  // Replies from executor object must go to this object
389  connect(executor, SIGNAL(reply(int,QString)), this, SLOT(message(int,QString)));
390  // Requests from this object must go to executor
391  connect(this, SIGNAL(cmdRequest(QString)), executor, SLOT(request(QString)));
392 
393  // On stopExecutor signal
394  // - queue executor for deletion (in execution thread)
395  // - quit the Qt event loop in the execution thread
396  connect(this, SIGNAL(stopExecutor()), executor, SLOT(deleteLater()));
397  connect(this, SIGNAL(stopExecutor()), thread, SLOT(quit()));
398  // Queue the thread for deletion (in this thread) when it is finished
399  connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
400 
401  // Default implementation of QThread::run() simply spins up an event loop in the thread,
402  // which is what we want.
403  thread->start();
404 }
405 
407 {
408  if(ui->tabWidget->widget(index) == ui->tab_console)
409  {
410  ui->lineEdit->setFocus();
411  }
412 }
413 
415 {
417 }
418 
420 {
421  QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar();
422  scrollbar->setValue(scrollbar->maximum());
423 }
424 
426 {
428  help.exec();
429 }
void openDebugLogfile()
Definition: guiutil.cpp:268
void setNumBlocks(int count, int countOfPeers)
Set number of blocks shown in the UI.
Definition: rpcconsole.cpp:337
Local Bitcoin RPC console.
Definition: rpcconsole.h:12
void on_lineEdit_returnPressed()
Definition: rpcconsole.cpp:346
Value help(const Array &params, bool fHelp)
Definition: bitcoinrpc.cpp:165
void message(int category, const QString &message, bool html=false)
Definition: rpcconsole.cpp:316
Definition: aboutdialog.h:6
const struct @14 ICON_MAPPING[]
QStringList history
Definition: rpcconsole.h:60
bool parseCommandLine(std::vector< std::string > &args, const std::string &strCommand)
Split shell command line into a list of arguments.
Definition: rpcconsole.cpp:65
void scrollToEnd()
Scroll console view to end.
Definition: rpcconsole.cpp:419
json_spirit::Value execute(const std::string &method, const json_spirit::Array &params) const
Execute a method.
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:150
QString formatClientStartupTime() const
void on_tabWidget_currentChanged(int index)
Definition: rpcconsole.cpp:406
Config::Object_type Object
Array RPCConvertValues(const std::string &strMethod, const std::vector< std::string > &strParams)
Convert parameter values for RPC call from strings to command-specific JSON objects.
void on_showCLOptionsButton_clicked()
display messagebox with program parameters (same as bitcoin-qt –help)
Definition: rpcconsole.cpp:425
void setClientModel(ClientModel *model)
Definition: rpcconsole.cpp:252
const char * url
Definition: rpcconsole.cpp:26
void reply(int category, const QString &command)
int getNumConnections() const
Definition: clientmodel.cpp:37
Value_type type() const
const char * source
Definition: rpcconsole.cpp:27
void browseHistory(int offset)
Go forward or back in history.
Definition: rpcconsole.cpp:369
const QSize ICON_SIZE(24, 24)
const int CONSOLE_HISTORY
Definition: rpcconsole.cpp:22
QDateTime getLastBlockDate() const
Definition: clientmodel.cpp:53
const Object_type::value_type::Value_type & find_value(const Object_type &obj, const String_type &name)
void request(const QString &command)
Definition: rpcconsole.cpp:135
MTState * state
Definition: db_test.cc:1708
QString clientName() const
int historyPtr
Definition: rpcconsole.h:61
const String_type & get_str() const
QString formatBuildDate() const
void on_openDebugLogfileButton_clicked()
open the debug.log from the current datadir
Definition: rpcconsole.cpp:414
Model for Bitcoin network client.
Definition: clientmodel.h:24
ClientModel * clientModel
Definition: rpcconsole.h:59
virtual bool eventFilter(QObject *obj, QEvent *event)
Definition: rpcconsole.cpp:216
Value_type::String_type write_string(const Value_type &value, bool pretty)
void setNumConnections(int count)
Set number of connections shown in the UI.
Definition: rpcconsole.cpp:332
void startExecutor()
Definition: rpcconsole.cpp:382
bool isTestNet() const
Return true if client connected to testnet.
void clear()
Definition: rpcconsole.cpp:283
Help message for Bitcoin-Qt, shown with –help.
Definition: guiutil.h:102
const CRPCTable tableRPC
RPCConsole(QWidget *parent=0)
Definition: rpcconsole.cpp:183
Ui::RPCConsole * ui
Definition: rpcconsole.h:58
void stopExecutor()
QString formatFullVersion() const
void cmdRequest(const QString &command)