RTXI  2.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
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);
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 
95 }
96 
98 {
100 }
101 
103 {
104 
105 #if LIBGIT2_SOVERSION >= 22
106  git_libgit2_init();
107 #else
108  git_threads_init();
109 #endif
110 
111  // syntax here only works in c++11
112  exclude_list = std::vector<QString> ({
113  QString("rtxi"),
114  QString("rtxi.github.io"),
115  QString("genicam-camera"),
116  QString("rtxi-crawler"),
117  QString("matlab-tools"),
118  QString("tutorials"),
119  QString("autapse"),
120  QString("camera-control"),
121  QString("gen-net"),
122  QString("dynamo-examples"),
123  QString("plot-lib"),
124  QString("python-plugin"),
125  QString("poster"),
126  QString("user-manual"),
127  QString("logos"),
128  QString("live-image"),
129  QString("conference-2015")
130  });
131  button_mode = DOWNLOAD;
132 
133 }
134 
135 // Set the text to "Update" if the module is already installed or "Download
136 // and Install" if not.
138 {
139  QListWidget *parent = qobject_cast<QListWidget*>(sender());
140 
141  if ( parent == availableListWidget )
142  {
143  cloneButton->setText("Install");
144  button_mode = DOWNLOAD;
145  }
146  else if ( parent == installedListWidget )
147  {
148  cloneButton->setText("Update");
149  button_mode = UPDATE;
150  }
151 
152 }
153 
154 // Clone the module currently highlighted in the QListWidget.
156 {
157  cloneButton->setEnabled(false);
158  availableListWidget->setDisabled(true);
159  installedListWidget->setDisabled(true);
160 
161  QString name;
162  switch(button_mode)
163  {
164  case DOWNLOAD:
165  name = availableListWidget->currentItem()->text();
166  break;
167 
168  case UPDATE:
169  name = installedListWidget->currentItem()->text();
170  break;
171 
172  default:
173  std::cout<<"ERROR: default in swtich block in cloneModule()"<<std::endl;
174  break;
175  }
176 
177  /*
178  * Two QByteArray variables are needed due to the way Qt stores binary data.
179  * Calling module->getCloneUrl().toString().toLatin1().data() will produce
180  * an error.
181  */
182 
183  QByteArray temp = modules[name].clone_url.toString().toLatin1();
184  const char *url = temp.data();
185  QByteArray temp2 = modules[name].location.toString().toLatin1();
186  const char *path = temp2.data();
187 
188  int error = 0;
189 
190  // If the repo already exists, pull from master. If not, clone it.
191  if ( (QDir(modules[name].location.toString())).exists() )
192  {
193  git_repository *repo = NULL;
194  git_remote *remote = NULL;
195 
196  git_repository_open(&repo, path);
197 #if LIBGIT2_SOVERSION >= 22
198  error = error | printGitError(git_remote_lookup(&remote, repo, "origin"));
199 #else
200  error = error | printGitError(git_remote_load(&remote, repo, "origin"));
201 #endif
202 
203 #if LIBGIT2_SOVERSION >= 24
204  error = error | printGitError(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL));
205 #else
206  error = error | printGitError(git_remote_connect(remote, GIT_DIRECTION_FETCH));
207 #endif
208 
209 #if LIBGIT2_SOVERSION >= 24
210  error = error | printGitError(git_remote_download(remote, NULL, NULL));
211 #elif LIBGIT2_SOVERSION >= 22
212  error = error | printGitError(git_remote_download(remote, NULL));
213 #elif LIBGIT2_SOVERSION >= 21
214  error = error | printGitError(git_remote_download(remote));
215 #else
216  error = error | printGitError(git_remote_download(remote, NULL, NULL));
217 #endif
218 
219  git_remote_disconnect(remote);
220  git_remote_free(remote);
221  git_repository_free(repo);
222  }
223  else
224  {
225  git_repository *repo = NULL;
226  printGitError(git_clone(&repo, url, path, NULL));
227  git_repository_free(repo);
228  }
229 
230  if (error)
231  {
232  std::cout<<"git ERROR"<<std::endl;
233  }
234  else
235  {
236  // Add module to list of already installed modules.
237  modules[name].installed = true;
238 
239  // Define the commands to be run.
240  QString make_cmd = "/usr/bin/make -j2 -C " + modules[name].location.toString();
241  QString make_install_cmd;
242 
243  // If RTXI is root, no need to call gksudo.
244  if (getuid()) make_install_cmd = "gksudo \"/usr/bin/make install -C" + modules[name].location.toString() + "\"";
245  else make_install_cmd = "/usr/bin/make install -C" + modules[name].location.toString();
246 
247  // Compile and instal handled by QProcess.
248  QProcess *make = new QProcess();
249  QProcess *make_install = new QProcess();
250  make->start(make_cmd);
251 
252  if (!make->waitForFinished())
253  {
254  std::cout<<"make -C "<<path<<" failed"<<std::endl;
255  }
256  else
257  {
258  make_install->start(make_install_cmd);
259  if (!make_install->waitForFinished())
260  {
261  std::cout<<"make install -C"<<path<<" failed..."<<std::endl;
262  std::cout<<"...despite make -C succeeding."<<std::endl;
263  }
264  }
265 
266  make->close();
267  make_install->close();
268  }
269 
270  // Re-enable buttons only after compilation is done. Otherwise you get race
271  // conditions if buttons are pressed before modules are done compiling.
272  cloneButton->setEnabled(true);
273  rebuildListWidgets();
274  availableListWidget->setDisabled(false);
275  installedListWidget->setDisabled(false);
276 }
277 
278 // Download the list of repos from GitHub's API. Call parseRepos for the JSON.
280 {
281  availableListWidget->setDisabled(true);
282  installedListWidget->setDisabled(true);
283 
284  if (!availableListWidget->count())
285  {
286  QUrl url("https://api.github.com/orgs/rtxi/repos?per_page=100");
287  reply = qnam.get(QNetworkRequest(url));
288  QObject::connect(reply, SIGNAL(finished()), this, SLOT(parseRepos(void)));
289  //connect(reply, SIGNAL(readyRead()), this, SLOT(httpReadyRead()));
290  //connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
291  // this, SLOT(updateDataReadProgress(qint64,qint64)));
292  }
293  else
294  {
295  availableListWidget->setDisabled(false);
296  installedListWidget->setDisabled(false);
297  }
298 }
299 
300 /*
301  * Download the README (markdown) for the highlighted repo just clicked. If the
302  * README has already been downloaded, the function will just keep that and
303  * not make another network request.
304  *
305  * The module doesn't save READMEs for after it closes. If you reopen the
306  * module, you'll have to redownload the repos.
307  */
309 {
310  availableListWidget->setDisabled(true);
311  installedListWidget->setDisabled(true);
312 
313  QListWidget *parent = qobject_cast<QListWidget*>(sender());
314  QString name = parent->currentItem()->text();
315 
316  // If the README hasn't been downloaded before, download it now.
317  if (modules[parent->currentItem()->text()].readme == "")
318  {
319  reply = qnam.get(QNetworkRequest(modules[name].readme_url));
320  QObject::connect(reply, SIGNAL(finished()), this, SLOT(parseReadme()));
321  }
322  else
323  {
324  // Disable buttons until all logic is done.
325  readmeWindow->setHtml(modules[parent->currentItem()->text()].readme);
326  cloneButton->setEnabled(true);
327  availableListWidget->setDisabled(false);
328  installedListWidget->setDisabled(false);
329  }
330 }
331 
332 // READMEs are downloaded as markdown. Convert them to HTML and display them
333 // within a QTextWidget.
335 {
336  const char* raw_data = (reply->readAll()).constData();
337  MMIOT *m = mkd_string(raw_data, strlen(raw_data), 0);
338  mkd_compile(m, 0);
339 
340  char* text;
341  int len = mkd_document(m, &text);
342  std::string html(text, text+len);
343 
344  mkd_cleanup(m);
345  QString fileText = QString::fromStdString(html);
346 
347  // QObject::disconnect(reply, SIGNAL(finished()), this, SLOT(parseReadme(void)));
348  reply->deleteLater();
349  reply = 0;
350 
351  switch(button_mode)
352  {
353  case DOWNLOAD:
354  modules[availableListWidget->currentItem()->text()].readme = fileText;
355  break;
356 
357  case UPDATE:
358  modules[installedListWidget->currentItem()->text()].readme = fileText;
359  break;
360 
361  default:
362  std::cout<<"ERROR: default in swtich block in cloneModule()"<<std::endl;
363  break;
364  }
365 
366  readmeWindow->setHtml(fileText);
367  readmeWindow->show();
368 
369  // The README is now displayed, so free the user to start clicking around.
370  cloneButton->setEnabled(true);
371  availableListWidget->setDisabled(false);
372  installedListWidget->setDisabled(false);
373 }
374 
375 // GitHub's API returns a JSON array. Parse it with QtJson functions.
377 {
378  QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll().data());
379  QJsonArray jsonArr = jsonDoc.array();
380 
381  QString readmeUrlPrefix = "https://raw.githubusercontent.com/RTXI/";
382  QString readmeUrlSuffix = "/master/README.md";
383  // QString locationPrefix = "/usr/local/lib/rtxi_modules/";
384 
385  QString locationPrefix;
386  if (getuid())
387  {
388  locationPrefix = QString(getenv("HOME")) + "/.config/RTXI/";
389  }
390  else
391  {
392  locationPrefix = "/usr/local/lib/rtxi_modules/";
393  }
394 
395  for (int idx = 0; idx < jsonArr.size(); idx++)
396  {
397  QJsonObject newObj = (jsonArr.at(idx)).toObject();
398  newObj.find("name").key();
399 
400  // if the current module isn't in the exclude_list
401  if (std::find(exclude_list.begin(), exclude_list.end(), newObj.value("name").toString()) == exclude_list.end())
402  {
403  module_t module;
404 
405  QString name = newObj.value("name").toString();
406  module.readme_url = QUrl(readmeUrlPrefix + newObj.value("name").toString() + readmeUrlSuffix);
407  module.clone_url = QUrl(newObj.value("clone_url").toString());
408  module.location = QString(locationPrefix + name);
409  module.readme = "";
410 
411  if ( (QDir(module.location.toString())).exists() )
412  {
413  module.installed = true;
414  }
415  else
416  {
417  module.installed = false;
418  }
419  modules[name] = module;
420  }
421  }
422 
423  // QObject::disconnect(reply, SIGNAL(finished()), this, SLOT(parseRepos(void)));
424  reply->deleteLater();
425  reply = 0;
426 
427  rebuildListWidgets();
428  availableListWidget->setDisabled(false);
429  installedListWidget->setDisabled(false);
430 }
431 
433 {
434  availableListWidget->clear();
435  installedListWidget->clear();
436 
437  for (std::map<QString,module_t>::iterator i = modules.begin(); i != modules.end(); ++i) {
438  if (i->second.installed) installedListWidget->addItem(i->first);
439  else availableListWidget->addItem(i->first);
440  }
441 
442  installedListWidget->sortItems(Qt::AscendingOrder);
443  availableListWidget->sortItems(Qt::AscendingOrder);
444 }
445 
446 
447 /*
448  * Public function, not for use in this module. It gets called by other
449  * classes. The function is basically a truncated version of cloneModule().
450  */
451 void RTXIWizard::Panel::installFromString( std::string module_name )
452 {
453  std::string cloneUrl = "https://github.com/rtxi/" + module_name;
454  // QString locationPrefix = "/usr/local/lib/rtxi_modules/";
455 
456  std::string locationUrl;
457  if (getuid())
458  {
459  locationUrl = std::string(getenv("HOME")) + "/.config/RTXI/" + module_name;
460  }
461  else
462  {
463  locationUrl = "/usr/local/lib/rtxi_modules/" + module_name;
464  }
465 
466  const char *url = cloneUrl.c_str();
467  const char *path = locationUrl.c_str();
468 
469  int error = 0;
470  if ( (QDir(QString::fromStdString(locationUrl))).exists() )
471  {
472  git_repository *repo = NULL;
473  git_remote *remote = NULL;
474 
475  git_repository_open(&repo, path);
476 #if LIBGIT2_SOVERSION >= 22
477  error = error | printGitError(git_remote_lookup(&remote, repo, "origin"));
478 #else
479  error = error | printGitError(git_remote_load(&remote, repo, "origin"));
480 #endif
481 
482 #if LIBGIT2_SOVERSION >= 24
483  error = error | printGitError(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL));
484 #else
485  error = error | printGitError(git_remote_connect(remote, GIT_DIRECTION_FETCH));
486 #endif
487 
488 #if LIBGIT2_SOVERSION >= 24
489  error = error | printGitError(git_remote_download(remote, NULL, NULL));
490 #elif LIBGIT2_SOVERSION >= 22
491  error = error | printGitError(git_remote_download(remote, NULL));
492 #elif LIBGIT2_SOVERSION >= 21
493  error = error | printGitError(git_remote_download(remote));
494 #else
495  error = error | printGitError(git_remote_download(remote, NULL, NULL));
496 #endif
497 
498  git_remote_disconnect(remote);
499  git_remote_free(remote);
500  git_repository_free(repo);
501 
502  }
503  else
504  {
505  git_repository *repo = NULL;
506  error = printGitError(git_clone(&repo, url, path, NULL));
507  git_repository_free(repo);
508  }
509 
510  if (error)
511  {
512  std::cout<<"git ERROR"<<std::endl;
513  return;
514  }
515 
516  QString make_cmd = "/usr/bin/make -j2 -C " + QString::fromStdString(locationUrl);
517  QString make_install_cmd;
518  if (getuid()) make_install_cmd = "gksudo \"/usr/bin/make install -C" + QString::fromStdString(locationUrl) + "\"";
519  else make_install_cmd = "/usr/bin/make install -C" + QString::fromStdString(locationUrl);
520 
521  QProcess *make = new QProcess();
522  QProcess *make_install = new QProcess();
523  make->start(make_cmd);
524 
525  if (!make->waitForFinished())
526  {
527  std::cout<<"make -C "<<path<<" failed"<<std::endl;
528  }
529  else
530  {
531  make_install->start(make_install_cmd);
532  if (!make_install->waitForFinished())
533  {
534  std::cout<<"make install -C"<<path<<" failed..."<<std::endl;
535  std::cout<<"...despite make -C succeeding."<<std::endl;
536  }
537  }
538 
539  make->close();
540  make_install->close();
541 
542 }
543 
545  if (error) {
546  const git_error *e = giterr_last();
547  printf("Error %d/%d: %s\n", error, e->klass, e->message);
548  }
549  return error;
550 }
551 
552 extern "C" Plugin::Object *createRTXIPlugin(void *)
553 {
555 }
556 
558 {
559  MainWindow::getInstance()->createSystemMenuItem("Plugin Manager",this,SLOT(showRTXIWizardPanel(void)));
560 }
561 
563 {
564  if(panel)
565  delete panel;
566  instance = 0;
567 }
568 
570 {
571  if(!panel)
572  panel = new Panel(MainWindow::getInstance()->centralWidget());
573  panel->show();
574 }
575 
577 {
578  if(p == panel)
579  panel = NULL;
580 }
581 
582 static Mutex mutex;
584 
586 {
587  if(instance)
588  return instance;
589 
590  /*************************************************************************
591  * Seems like alot of hoops to jump through, but allocation isn't *
592  * thread-safe. So effort must be taken to ensure mutual exclusion. *
593  *************************************************************************/
594 
595  Mutex::Locker lock(&::mutex);
596  if(!instance)
597  instance = new Plugin();
598 
599  return instance;
600 }
static MainWindow * getInstance(void)
static Plugin * instance
Definition: rtxi_wizard.h:97
Plugin::Object * createRTXIPlugin(void *)
QPushButton * syncButton
Definition: rtxi_wizard.h:73
void getReadme(void)
Panel(QWidget *)
Definition: rtxi_wizard.cpp:30
void rebuildListWidgets(void)
int printGitError(int)
Definition: mutex.h:28
void parseReadme(void)
virtual ~Panel(void)
Definition: rtxi_wizard.cpp:97
void getRepos(void)
void initParameters(void)
QMdiSubWindow * subWindow
Definition: rtxi_wizard.h:62
void updateButton(void)
void createMdi(QMdiSubWindow *)
void removeRTXIWizardPanel(Panel *)
void showRTXIWizardPanel(void)
QTextEdit * readmeWindow
Definition: rtxi_wizard.h:68
QListWidget * installedListWidget
Definition: rtxi_wizard.h:70
void installFromString(std::string)
QListWidget * availableListWidget
Definition: rtxi_wizard.h:69
QPushButton * cloneButton
Definition: rtxi_wizard.h:72
void cloneModule(void)
QAction * createSystemMenuItem(const QString &label, const QObject *handler, const char *slot)
void parseRepos(void)
Classes associated with the loading/unloading of binaries at run-time.
Definition: plugin.h:35
static Plugin * getInstance(void)