PageRenderTime 62ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/src/qt/rpcconsole.cpp

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