RTXI  2.4
The Real-Time eXperiment Interface Documentation
rtxi_wizard.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 Georgia Institute of Technology, University of Utah, Weill Cornell Medical College
3 
4  This program is free software: you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation, either version 3 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <iostream>
19 #include <unistd.h>
20 #include <git2.h>
21 extern "C" {
22 #include <mkdio.h>
23 }
24 #include "rtxi_wizard.h"
25 
26 /*
27  * This module uses the QNetworkManager class to fetch information on our
28  * GitHub repos using GitHub's own API.
29  */
30 RTXIWizard::Panel::Panel(QWidget *parent) : QWidget(parent)
31 {
32  setWhatsThis("Module Wizard enables management of all RTXI modules. You can download and install new modules directly from the GitHub site..etc");
33 
34  // Make Mdi
35  subWindow = new QMdiSubWindow;
36  subWindow->setWindowIcon(QIcon("/usr/local/share/rtxi/RTXI-widget-icon.png"));
37  subWindow->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint |
38  Qt::WindowMinimizeButtonHint);
39  subWindow->setAttribute(Qt::WA_DeleteOnClose);
40  MainWindow::getInstance()->createMdi(subWindow);
41 
42  QGridLayout *customLayout = new QGridLayout;
43 
44  QGroupBox *buttonBox = new QGroupBox;
45  QHBoxLayout *buttonLayout = new QHBoxLayout();
46  buttonBox->setLayout(buttonLayout);
47  syncButton = new QPushButton("Sync", this);
48  cloneButton = new QPushButton("Install", this);
49  cloneButton->setEnabled(false);
50  buttonLayout->addWidget(syncButton);
51  buttonLayout->addWidget(cloneButton);
52 
53  QGroupBox *installedBox = new QGroupBox("Installed");
54  QVBoxLayout *installedLayout = new QVBoxLayout;
55  installedBox->setLayout(installedLayout);
56  installedListWidget = new QListWidget(installedBox);
57  installedListWidget->setFixedWidth(175);
58  installedListWidget->setSortingEnabled(true);
59  installedLayout->addWidget(installedListWidget);
60 
61  QGroupBox *moduleBox = new QGroupBox("Available");
62  QVBoxLayout *moduleLayout = new QVBoxLayout;
63  moduleBox->setLayout(moduleLayout);
64  availableListWidget = new QListWidget(this);
65  availableListWidget->setFixedWidth(175);
66  availableListWidget->setSortingEnabled(true);
67  moduleLayout->addWidget(availableListWidget);
68 
69  readmeWindow = new QTextEdit;
70  readmeWindow->setReadOnly(true);
71  readmeWindow->show();
72 
73  customLayout->addWidget(buttonBox, 0, 0);
74  customLayout->addWidget(moduleBox, 1, 0);
75  customLayout->addWidget(installedBox, 2, 0);
76  customLayout->addWidget(readmeWindow, 0, 1, 3, 1);
77  customLayout->setColumnStretch(0, 0);
78  customLayout->setColumnStretch(1, 1);
79 
80  QObject::connect(syncButton, SIGNAL(clicked()), this, SLOT(getRepos()));
81  QObject::connect(cloneButton, SIGNAL(clicked()), this, SLOT(cloneModule()));
82  QObject::connect(availableListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(getReadme(void)));
83  QObject::connect(availableListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(updateButton(void)));
84  QObject::connect(installedListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(getReadme(void)));
85  QObject::connect(installedListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(updateButton(void)));
86 
87  setLayout(customLayout);
88  setWindowTitle("Module Wizard");
89  subWindow->setWidget(this);
90  subWindow->resize(700, subWindow->sizeHint().height());
91  getRepos();
92  show();
93 
94  initParameters();
95 }
96 
98 {
99  Plugin::getInstance()->removeRTXIWizardPanel(this);
100 }
101 
102 void RTXIWizard::Panel::initParameters(void)
103 {
104 
105  git_libgit2_init();
106 
107  // syntax here only works in c++11
108  exclude_list = std::vector<QString> ({
109  QString("rtxi"),
110  QString("rtxi.github.io"),
111  QString("genicam-camera"),
112  QString("rtxi-crawler"),
113  QString("matlab-tools"),
114  QString("tutorials"),
115  QString("autapse"),
116  QString("camera-control"),
117  QString("gen-net"),
118  QString("dynamo-examples"),
119  QString("plot-lib"),
120  QString("python-plugin"),
121  QString("poster"),
122  QString("user-manual"),
123  QString("logos"),
124  QString("live-image"),
125  QString("conference-2015")
126  });
127  button_mode = DOWNLOAD;
128 
129 }
130 
131 // Set the text to "Update" if the module is already installed or "Download
132 // and Install" if not.
133 void RTXIWizard::Panel::updateButton(void)
134 {
135  QListWidget *parent = qobject_cast<QListWidget*>(sender());
136 
137  if ( parent == availableListWidget )
138  {
139  cloneButton->setText("Install");
140  button_mode = DOWNLOAD;
141  }
142  else if ( parent == installedListWidget )
143  {
144  cloneButton->setText("Update");
145  button_mode = UPDATE;
146  }
147 
148 }
149 
150 // Clone the module currently highlighted in the QListWidget.
151 void RTXIWizard::Panel::cloneModule(void)
152 {
153  cloneButton->setEnabled(false);
154  availableListWidget->setDisabled(true);
155  installedListWidget->setDisabled(true);
156 
157  // Let the user know that RTXI is installing the plugin.
158  QProgressDialog *progress = new QProgressDialog("Installing plugin", "Cancel", 0, 4, this);
159  progress->setWindowModality(Qt::WindowModal);
160  progress->show();
161  progress->setLabelText("Configuring...");
162  QApplication::processEvents();
163 
164  QString name;
165  switch(button_mode)
166  {
167  case DOWNLOAD:
168  name = availableListWidget->currentItem()->text();
169  break;
170 
171  case UPDATE:
172  name = installedListWidget->currentItem()->text();
173  break;
174 
175  default:
176  std::cout<<"ERROR: default in switch block in cloneModule()"<<std::endl;
177  break;
178  }
179 
180  /*
181  * Two QByteArray variables are needed due to the way Qt stores binary data.
182  * Calling module->getCloneUrl().toString().toLatin1().data() will produce
183  * an error.
184  */
185 
186  QByteArray temp = modules[name].clone_url.toString().toLatin1();
187  const char *url = temp.data();
188  QByteArray temp2 = modules[name].location.toString().toLatin1();
189  const char *path = temp2.data();
190 
191  int error = 0;
192 
193  progress->setLabelText("Downloading extension...");
194  progress->setValue(1);
195  QApplication::processEvents();
196  // If the repo already exists, pull from master. If not, clone it.
197  if ( (QDir(modules[name].location.toString())).exists() )
198  {
199  git_repository *repo = NULL;
200  git_remote *remote = NULL;
201 
202  git_repository_open(&repo, path);
203  printGitError(git_remote_lookup(&remote, repo, "origin"));
204 
205  printGitError(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
206 
207  printGitError(git_remote_download(remote, NULL, NULL));
208 
209  git_remote_disconnect(remote);
210  git_remote_free(remote);
211  git_repository_free(repo);
212  }
213  else
214  {
215  git_repository *repo = NULL;
216  printGitError(git_clone(&repo, url, path, NULL));
217  git_repository_free(repo);
218  }
219 
220  if (error)
221  {
222  std::cout<<"git ERROR"<<std::endl;
223  }
224  else
225  {
226  progress->setLabelText("Building...");
227  progress->setValue(2);
228  QApplication::processEvents();
229 
230 
231  // Define the commands to be run.
232  QString make_cmd = "/usr/bin/make -j2 -C " + modules[name].location.toString();
233  QString make_install_cmd;
234 
235 
236  // If RTXI is root, no need to call gksudo.
237  if (getuid()) make_install_cmd = "gksudo \"/usr/bin/make install -C" + modules[name].location.toString() + "\"";
238  else make_install_cmd = "/usr/bin/make install -C" + modules[name].location.toString();
239 
240  // Compile and instal handled by QProcess.
241  QProcess *make = new QProcess();
242  QProcess *make_install = new QProcess();
243  make->start(make_cmd);
244 
245  if (!make->waitForFinished())
246  {
247  QMessageBox *errmessage;
248  errmessage->critical(0, "Error", "Could not compile plugin. Email help@rtxi.org for assistance");
249  std::cout<<"make -C "<<path<<" failed"<<std::endl;
250  }
251  else
252  {
253  progress->setLabelText("Installing binaries...");
254  progress->setValue(3);
255  QApplication::processEvents();
256  make_install->start(make_install_cmd);
257  if (!make_install->waitForFinished())
258  {
259  std::cout<<"make install -C"<<path<<" failed..."<<std::endl;
260  std::cout<<"...despite make -C succeeding."<<std::endl;
261  } else {
262  // Add module to list of already installed modules.
263  modules[name].installed = true;
264  }
265 
266  }
267 
268  progress->setValue(4);
269  QApplication::processEvents();
270  make->close();
271  make_install->close();
272  }
273 
274  // Re-enable buttons only after compilation is done. Otherwise you get race
275  // conditions if buttons are pressed before modules are done compiling.
276  cloneButton->setEnabled(true);
277  rebuildListWidgets();
278  availableListWidget->setDisabled(false);
279  installedListWidget->setDisabled(false);
280 }
281 
282 // Download the list of repos from GitHub's API. Call parseRepos for the JSON.
283 void RTXIWizard::Panel::getRepos()
284 {
285  availableListWidget->setDisabled(true);
286  installedListWidget->setDisabled(true);
287 
288  if (!availableListWidget->count())
289  {
290  QUrl url("https://api.github.com/orgs/rtxi/repos?per_page=100");
291  reply = qnam.get(QNetworkRequest(url));
292  QObject::connect(reply, SIGNAL(finished()), this, SLOT(parseRepos(void)));
293  //connect(reply, SIGNAL(readyRead()), this, SLOT(httpReadyRead()));
294  //connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
295  // this, SLOT(updateDataReadProgress(qint64,qint64)));
296  }
297  else
298  {
299  availableListWidget->setDisabled(false);
300  installedListWidget->setDisabled(false);
301  }
302 }
303 
304 /*
305  * Download the README (markdown) for the highlighted repo just clicked. If the
306  * README has already been downloaded, the function will just keep that and
307  * not make another network request.
308  *
309  * The module doesn't save READMEs for after it closes. If you reopen the
310  * module, you'll have to redownload the repos.
311  */
312 void RTXIWizard::Panel::getReadme(void)
313 {
314  availableListWidget->setDisabled(true);
315  installedListWidget->setDisabled(true);
316  QListWidget *parent = qobject_cast<QListWidget*>(sender());
317  QString name = parent->currentItem()->text();
318 
319  // If the README hasn't been downloaded before, download it now.
320  if (modules[parent->currentItem()->text()].readme == "")
321  {
322  reply = qnam.get(QNetworkRequest(modules[name].readme_url));
323  QObject::connect(reply, SIGNAL(finished()), this, SLOT(parseReadme()));
324 
325  }
326  else
327  {
328  // Disable buttons until all logic is done.
329  readmeWindow->setHtml(modules[parent->currentItem()->text()].readme);
330  cloneButton->setEnabled(true);
331  availableListWidget->setDisabled(false);
332  installedListWidget->setDisabled(false);
333  }
334 }
335 
336 // READMEs are downloaded as markdown. Convert them to HTML and display them
337 // within a QTextWidget.
338 void RTXIWizard::Panel::parseReadme(void)
339 {
340  const char* raw_data = (reply->readAll()).constData();
341  MMIOT *m = mkd_string(raw_data, strlen(raw_data), 0);
342  mkd_compile(m, 0);
343 
344  char* text;
345  int len = mkd_document(m, &text);
346  std::string html(text, text+len);
347 
348  mkd_cleanup(m);
349  QString fileText = QString::fromStdString(html);
350 
351  //reply->deleteLater();
352  reply = 0;
353 
354  switch(button_mode)
355  {
356  case DOWNLOAD:
357  modules[availableListWidget->currentItem()->text()].readme = fileText;
358  break;
359 
360  case UPDATE:
361  modules[installedListWidget->currentItem()->text()].readme = fileText;
362  break;
363 
364  default:
365  std::cout<<"ERROR: default in swtich block in cloneModule()"<<std::endl;
366  break;
367  }
368 
369  readmeWindow->setHtml(fileText);
370  readmeWindow->show();
371 
372  // The README is now displayed, so free the user to start clicking around.
373  cloneButton->setEnabled(true);
374  availableListWidget->setDisabled(false);
375  installedListWidget->setDisabled(false);
376 }
377 
378 // GitHub's API returns a JSON array. Parse it with QtJson functions.
379 void RTXIWizard::Panel::parseRepos(void)
380 {
381  QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll().data());
382  QJsonArray jsonArr = jsonDoc.array();
383 
384  QString readmeUrlPrefix = "https://raw.githubusercontent.com/RTXI/";
385  QString readmeUrlSuffix = "/master/README.md";
386  // QString locationPrefix = "/usr/local/lib/rtxi_modules/";
387 
388  QString locationPrefix;
389  if (getuid())
390  {
391  locationPrefix = QString(getenv("HOME")) + "/.config/RTXI/";
392  }
393  else
394  {
395  locationPrefix = "/usr/local/lib/rtxi_modules/";
396  }
397 
398  for (int idx = 0; idx < jsonArr.size(); idx++)
399  {
400  QJsonObject newObj = (jsonArr.at(idx)).toObject();
401  newObj.find("name").key();
402 
403  // if the current module isn't in the exclude_list
404  if (std::find(exclude_list.begin(), exclude_list.end(), newObj.value("name").toString()) == exclude_list.end())
405  {
406  module_t module;
407 
408  QString name = newObj.value("name").toString();
409  module.readme_url = QUrl(readmeUrlPrefix + newObj.value("name").toString() + readmeUrlSuffix);
410  module.clone_url = QUrl(newObj.value("clone_url").toString());
411  module.location = QString(locationPrefix + name);
412  module.readme = "";
413 
414  if ( (QDir(module.location.toString())).exists() )
415  {
416  module.installed = true;
417  }
418  else
419  {
420  module.installed = false;
421  }
422  modules[name] = module;
423  }
424  }
425 
426  // QObject::disconnect(reply, SIGNAL(finished()), this, SLOT(parseRepos(void)));
427  reply->deleteLater();
428  reply = 0;
429 
430  rebuildListWidgets();
431  availableListWidget->setDisabled(false);
432  installedListWidget->setDisabled(false);
433 }
434 
436 {
437  availableListWidget->clear();
438  installedListWidget->clear();
439 
440  for (std::map<QString,module_t>::iterator i = modules.begin(); i != modules.end(); ++i) {
441  if (i->second.installed) installedListWidget->addItem(i->first);
442  else availableListWidget->addItem(i->first);
443  }
444 
445  installedListWidget->sortItems(Qt::AscendingOrder);
446  availableListWidget->sortItems(Qt::AscendingOrder);
447 }
448 
449 
450 /*
451  * Public function, not for use in this module. It gets called by other
452  * classes. The function is basically a truncated version of cloneModule().
453  */
454 void RTXIWizard::Panel::installFromString( std::string module_name )
455 {
456  QProcess *make = new QProcess();
457  QProcess *make_install = new QProcess();
458 
459  // Let the user know that RTXI is installing the plugin.
460  QProgressDialog *progress = new QProgressDialog("Installing plugin", "Cancel", 0, 5, this);
461  progress->setWindowModality(Qt::WindowModal);
462  progress->setLabelText("Configuring...");
463  std::string cloneUrl = "https://github.com/rtxi/" + module_name;
464 
465  std::string locationUrl;
466  if (getuid())
467  {
468  locationUrl = std::string(getenv("HOME")) + "/.config/RTXI/" + module_name;
469  }
470  else
471  {
472  locationUrl = "/usr/local/lib/rtxi_modules/" + module_name;
473  }
474 
475  const char *url = cloneUrl.c_str();
476  const char *path = locationUrl.c_str();
477 
478  progress->setLabelText("Downloading Plugin...");
479  progress->setValue(1);
480  int error = 0;
481  if ( (QDir(QString::fromStdString(locationUrl))).exists() )
482  {
483  git_repository *repo = NULL;
484  git_remote *remote = NULL;
485 
486  git_repository_open(&repo, path);
487  printGitError(git_remote_lookup(&remote, repo, "origin"));
488 
489  printGitError(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
490 
491  printGitError(git_remote_download(remote, NULL, NULL));
492 
493  git_remote_disconnect(remote);
494  git_remote_free(remote);
495  git_repository_free(repo);
496 
497  }
498  else
499  {
500  git_repository *repo = NULL;
501  printGitError(git_clone(&repo, url, path, NULL));
502  git_repository_free(repo);
503  }
504 
505  if (error)
506  {
507  std::cout<<"git ERROR"<<std::endl;
508  return;
509  }
510 
511  QString make_cmd = "/usr/bin/make -j2 -C " + QString::fromStdString(locationUrl);
512  QString make_install_cmd;
513  if (getuid()) make_install_cmd = "gksudo \"/usr/bin/make install -C" + QString::fromStdString(locationUrl) + "\"";
514  else make_install_cmd = "/usr/bin/make install -C" + QString::fromStdString(locationUrl);
515 
516  progress->setLabelText("Building...");
517  progress->setValue(2);
518  make->start(make_cmd);
519 
520  progress->setLabelText("Installing extension...");
521  progress->setValue(3);
522  if (!make->waitForFinished())
523  {
524  std::cout<<"make -C "<<path<<" failed"<<std::endl;
525  }
526  else
527  {
528  make_install->start(make_install_cmd);
529  if (!make_install->waitForFinished())
530  {
531  std::cout<<"make install -C"<<path<<" failed..."<<std::endl;
532  std::cout<<"...despite make -C succeeding."<<std::endl;
533  }
534  }
535 
536  make->close();
537  make_install->close();
538  progress->setValue(5);
539 }
540 
541 int RTXIWizard::Panel::printGitError(int error) {
542  if (error) {
543  const git_error *e = giterr_last();
544  printf("Error %d/%d: %s\n", error, e->klass, e->message);
545  }
546  return error;
547 }
548 
549 extern "C" Plugin::Object *createRTXIPlugin(void *)
550 {
552 }
553 
554 RTXIWizard::Plugin::Plugin(void) : panel(0)
555 {
556  MainWindow::getInstance()->createSystemMenuItem("Plugin Manager",this,SLOT(showRTXIWizardPanel(void)));
557 }
558 
559 RTXIWizard::Plugin::~Plugin(void)
560 {
561  if(panel)
562  delete panel;
563  instance = 0;
564 }
565 
567 {
568  if(!panel)
569  panel = new Panel(MainWindow::getInstance()->centralWidget());
570  panel->show();
571 }
572 
573 void RTXIWizard::Plugin::removeRTXIWizardPanel(RTXIWizard::Panel *p)
574 {
575  if(p == panel)
576  panel = NULL;
577 }
578 
579 static Mutex mutex;
580 RTXIWizard::Plugin *RTXIWizard::Plugin::instance = 0;
581 
583 {
584  if(instance)
585  return instance;
586 
587  /*************************************************************************
588  * Seems like alot of hoops to jump through, but allocation isn't *
589  * thread-safe. So effort must be taken to ensure mutual exclusion. *
590  *************************************************************************/
591 
592  Mutex::Locker lock(&::mutex);
593  if(!instance)
594  instance = new Plugin();
595 
596  return instance;
597 }
MainWindow::createSystemMenuItem
QAction * createSystemMenuItem(const QString &label, const QObject *handler, const char *slot)
Definition: main_window.cpp:253
rtxi_wizard.h
RTXIWizard::Plugin::getInstance
static Plugin * getInstance(void)
Definition: rtxi_wizard.cpp:582
RTXIWizard::Plugin
Definition: rtxi_wizard.h:78
Plugin::Object
Definition: plugin.h:145
MainWindow::createMdi
void createMdi(QMdiSubWindow *)
Definition: main_window.cpp:230
RTXIWizard::Panel
Definition: rtxi_wizard.h:28
RTXIWizard::Plugin::showRTXIWizardPanel
void showRTXIWizardPanel(void)
Definition: rtxi_wizard.cpp:566
createRTXIPlugin
Plugin::Object * createRTXIPlugin(void *)
Definition: rtxi_wizard.cpp:549
Mutex
Definition: mutex.h:28
RTXIWizard::Panel::Panel
Panel(QWidget *)
Definition: rtxi_wizard.cpp:30
RTXIWizard::Panel::installFromString
void installFromString(std::string)
Definition: rtxi_wizard.cpp:454
RTXIWizard::Panel::~Panel
virtual ~Panel(void)
Definition: rtxi_wizard.cpp:97
Plugin
Classes associated with the loading/unloading of binaries at run-time.
Definition: plugin.h:35
Mutex::Locker
Definition: mutex.h:36
RTXIWizard::Panel::rebuildListWidgets
void rebuildListWidgets(void)
Definition: rtxi_wizard.cpp:435
MainWindow::getInstance
static MainWindow * getInstance(void)
Definition: main_window.cpp:454