tuchulcha  0.10.1
Graphical Workflow Configuration Editor
LogDialog.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2011 Jens-Malte Gottfried
2 
3  This file is part of Tuchulcha.
4 
5  Tuchulcha is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  Tuchulcha is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public License
16  along with Tuchulcha. If not, see <http://www.gnu.org/licenses/>.
17  */
25 #include "LogDialog.h"
26 #include "ui_LogDialog.h"
27 #include "LogViewProxyModel.h"
28 #include "LogDecorators.h"
29 
30 #include <QTextStream>
31 #include <QScrollBar>
32 #include <QTimer>
33 #include <QMessageBox>
34 #include <QSettings>
35 #include <QFileInfo>
36 #include <QFileDialog>
37 #include <QMutexLocker>
38 #include <QStringListModel>
39 #include <QMimeData>
40 #include <QStandardItemModel>
41 #include <QListView>
42 
45  QWidget* pp, Qt::WindowFlags wf) :
46  QDialog(pp,wf), _log(0), _logProx(0), _decorator(dec), _proc(0),
47  _logFile(0), _logMutex(new QMutex()), _errorLinesDetected(false)
48 {
49  _proc = new QProcess(this);
50  _proc->setObjectName("proc");
51  _ui = new Ui::LogDialog;
52  _ui->setupUi(this);
53 #ifdef USE_ASSISTANT
54  _ui->buttonBox->setStandardButtons(
55  _ui->buttonBox->standardButtons() | QDialogButtonBox::Help);
56 #endif
57 
58  // set up log models
59  _log = new QStringListModel(this);
61  _logProx->setSourceModel(_log);
62  QFont ttFont("fixed"); ttFont.setStyleHint(QFont::TypeWriter);
63  _ui->logView->setFont(ttFont);
64  _ui->logView->setModel(_logProx);
65 
66  loadSettings();
67 
68  // set up decorator and connections
69  _decorator->debugOutput = _ui->checkDD->isChecked();
70  connect(_decorator, SIGNAL(finish()), SLOT(on_proc_finished()));
71  connect(_decorator, SIGNAL(message(QString)), SLOT(printStatus(QString)));
72  connect(_decorator, SIGNAL(filter(QString)), SLOT(searchLog(QString)));
73 
74  QString title=_decorator->title();
75  QString desc=_decorator->desc();
76  if (!title.isEmpty()) {
77  setWindowTitle(title);
78  }
79  if (!desc.isEmpty()) {
80  _ui->lInfo->setText(desc);
81  }
82  QWidget* statWid = _decorator->statusWidget();
83  if (statWid) {
84  _ui->statusLayout->addWidget(statWid);
85  }
86 
87  _ui->infoDisplay->setVisible(false);
88 
89  // determine available run process executables
90  QStringList testArgs;
91  testArgs << "--quiet" << "--non-interactive";
92  QString tcRun = QApplication::applicationDirPath() + "/tuchulcha-run";
93  if (QProcess::execute(tcRun,testArgs)) {
94  tcRun = QString::null;
95  }
96  QString tcRunD = QApplication::applicationDirPath() + "/tuchulcha-run_d";
97  if (QProcess::execute(tcRunD,testArgs)) {
98  tcRunD = QString::null;
99  }
100 
101  // select process executable
102  QString procName = tcRun;
103  QSettings settings;
104  if ((!tcRunD.isNull()
105  && settings.value("suffixedPlugins", false).toBool())
106  || tcRun.isNull()) {
107  procName = tcRunD;
108  }
109 
110  if (procName.isNull()) {
111  // warn if no valid executable found
112  printStatus(QString("<br><span class=\"error\">%1</span></br>")
113  .arg(tr("no working %1 process executable found")
114  .arg("<tt>tuchulcha-run</tt>")));
115  _ui->lProcName->setText(
116  tr("Executable:")+
117  QString(" <span style=\"font-weight:bold;color:red\">(%1)</span>")
118  .arg(tr("none")));
119  return;
120  }
121 
122  QString procText =
123  tr("Executable: ")
124  +QString(" <span style=\"color:blue\"><tt>%1</tt></span>")
125  .arg(QFileInfo(procName).baseName());
126 
127  // open log file
128  _logFile = new QFile(_decorator->logFileName(), this);
129  _logFile->open(QIODevice::WriteOnly|QIODevice::Truncate|QIODevice::Text);
130  procText = QString("%1 %2: <span style=\"color:blue\"><tt>%3</tt></span>")
131  .arg(procText).arg(tr("Log"))
132  .arg(QFileInfo(_logFile->fileName()).fileName());
133  _ui->lProcName->setText(procText);
134 
135  if(_decorator->ready(this)) {
136  // start process
137  _proc->start(
138  procName, _decorator->arguments(),
139  QIODevice::ReadWrite|QIODevice::Text);
140  #ifdef Q_OS_LINUX
141  procText =
142  QString("%1 PID: <span style=\"color:blue\"><tt>%2</tt></span>")
143  .arg(procText).arg(_proc->pid());
144  _ui->lProcName->setText(procText);
145  #endif
146  }
147  else {
148  // close dialog
149  QTimer::singleShot(0,this,SLOT(reject()));
150  }
151 
152  // set up status widget
153  _ui->infoDisplay->document()->setDefaultStyleSheet(
154  "*{white-space:pre;font-weight:normal}\n"
155  "h3{font-weight:bold;font-variant:small-caps}\n"
156  ".file {font-family:monospace;}\n"
157  "td {padding: 0 4px;}\n"
158  ".error {color:red;font-weight:bold;}\n"
159  ".success {color:green;}\n"
160  ".warning {color:orange;}\n"
161  ".info {color:#444;}\n"
162  ".debug {color:gray;}\n"
163  );
164 }
165 
166 LogDialog::~LogDialog() {
167  saveSettings();
168  delete _ui;
169  delete _decorator;
170  delete _logMutex;
171 }
172 
174  // apply saved settings, use values from UI as fallback
175  QSettings settings;
176  settings.beginGroup("LogDialog");
177  _ui->checkDD->setChecked(settings.value("showDebugOutput",
178  _ui->checkDD->isChecked()).toBool());
179  _ui->checkScroll->setChecked(settings.value("autoScroll",
180  _ui->checkScroll->isChecked()).toBool());
181  _ui->sBufSize->setValue(settings.value("maxLines",
182  _ui->sBufSize->value()).toInt());
183  settings.endGroup();
184 }
185 
187  QSettings settings;
188  settings.beginGroup("LogDialog");
189  settings.setValue("showDebugOutput",_ui->checkDD->isChecked());
190  settings.setValue("autoScroll",_ui->checkScroll->isChecked());
191  settings.setValue("maxLines",_ui->sBufSize->value());
192  settings.endGroup();
193 }
194 
195 void LogDialog::done(int r) {
196  // terminate process if still running
197 
198  if (_proc && (_proc->state() != QProcess::NotRunning)) {
199  printStatus(
200  QString("<br><span class=\"warning\">%1</span><br>")
201  .arg(tr("waiting for process to quit..."))+
202  QString("<span class=\"warning\">%1</span><br>")
203  .arg(tr("asking for termination in 1 second")));
204  _proc->write("quit\n");
205  connect(_proc,SIGNAL(finished(int)),SLOT(close()));
206  QTimer::singleShot(1000,this,SLOT(terminate()));
207  }
208  else {
209  // quit event loop and return
210  QDialog::done(r);
211  }
212 }
213 
214 void LogDialog::terminate(bool force) {
215  if (_proc && (_proc->state() != QProcess::NotRunning)
216  && (force || QMessageBox::question(
217  this,tr("confirm terminate"),
218  tr("Process still running.<br>Terminate running process?"),
219  QMessageBox::No,QMessageBox::Yes)==QMessageBox::Yes)) {
220  printStatus(QString("<span class=\"warning\">%1</span><br>")
221  .arg(tr("asking for kill in 3 seconds")));
222  QTimer::singleShot(0,_proc,SLOT(terminate()));
223  QTimer::singleShot(3000,this,SLOT(kill()));
224  }
225 }
226 
227 void LogDialog::kill(bool force) {
228  if (_proc && (_proc->state() != QProcess::NotRunning)
229  && (force || QMessageBox::question(
230  this,tr("confirm kill"),
231  tr("Process did not respond.<br>Kill running process?"),
232  QMessageBox::No,QMessageBox::Yes)==QMessageBox::Yes)) {
233  _proc->kill();
234  }
235 }
236 
237 bool LogDialog::waitForFinished(int msecs) {
238  if (_proc->state() == QProcess::Starting) {
239  _proc->waitForStarted(msecs);
240  }
241  if (_proc->state() == QProcess::Running) {
242  return _proc->waitForFinished(msecs);
243  }
244  return false;
245 }
246 
248  QMutexLocker mLock(_logMutex);
249  QString origS = QString::fromLocal8Bit(_proc->readAllStandardOutput());
250  QString cur;
251  QTextStream orig(&origS,QIODevice::ReadOnly);
252  QStringList logList = _log->stringList();
253  QTextStream logFile(_logFile);
254  const int maxBuf = _ui->sBufSize->value();
255 
256  forever {
257  cur = orig.readLine();
258  if (cur.isNull()) {
259  break;
260  }
261  logFile << cur << endl;
262  _decorator->processLine(cur);
263  if (cur.contains(
264  QRegExp("^\\(EE\\)\\s+",Qt::CaseInsensitive))) {
265  _errorLinesDetected = true;
266  }
267  logList << cur;
268  if (maxBuf && logList.size() > maxBuf) {
269  logList.removeFirst();
270  }
271  }
272  _log->setStringList(logList);
273 
274  if (_ui->checkScroll->isChecked()) {
275  _ui->logView->scrollToBottom();
276  }
277 
278  mLock.unlock();
279 }
280 
282  _errorLinesDetected = true;
283  QMutexLocker mLock(_logMutex);
284  QString origS = QString::fromLocal8Bit(_proc->readAllStandardError());
285  QString cur;
286  QTextStream orig(&origS,QIODevice::ReadOnly);
287  QStringList logList = _log->stringList();
288  QTextStream logFile(_logFile);
289  const int maxBuf = _ui->sBufSize->value();
290 
291  forever {
292  cur = orig.readLine();
293  if (cur.isNull()) {
294  break;
295  }
296  if (!cur.contains(
297  QRegExp("^\\(\\w+\\)\\s+",Qt::CaseInsensitive))) {
298  cur = QString("(EE) %1").arg(cur);
299  }
300  logFile << cur << endl;
301  _decorator->processLine(cur);
302  logList << cur;
303  if (maxBuf && logList.size() > maxBuf) {
304  logList.removeFirst();
305  }
306  }
307 
308  _log->setStringList(logList);
309 
310  if (_ui->checkScroll->isChecked()) {
311  _ui->logView->scrollToBottom();
312  }
313 
314  mLock.unlock();
315 }
316 
318  return _errorLinesDetected;
319 }
320 
322  if (!_logFile || !_logFile->isOpen()) {
323  // reprint only useful after initialization
324  return;
325  }
326  QMutexLocker mLock(_logMutex);
327  QString cur;
328  _logFile->close();
329  _logFile->open(QIODevice::ReadOnly|QIODevice::Text);
330  QTextStream log(_logFile);
331  QStringList logList;
332  const int maxBuf = _ui->sBufSize->value();
333 
334  forever {
335  cur = log.readLine();
336  if (cur.isNull()) {
337  break;
338  }
339  logList << cur;
340  if (maxBuf && logList.size() > maxBuf) {
341  logList.removeFirst();
342  }
343  }
344  _log->setStringList(logList);
345 
346  if (_ui->checkScroll->isChecked()) {
347  _ui->logView->scrollToBottom();
348  }
349 
350  _logFile->close();
351  _logFile->open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text);
352 
353  mLock.unlock();
354 }
355 
357  _ui->progressBar->show();
358 #ifdef USE_ASSISTANT
359  _ui->buttonBox->setStandardButtons(
360  QDialogButtonBox::Abort|QDialogButtonBox::Help);
361 #else
362  _ui->buttonBox->setStandardButtons(QDialogButtonBox::Abort);
363 #endif
364 
365  QStringList postStart = _decorator->postStartCommands(this);
366  QTextStream pout(_proc);
367  foreach(const QString& cmd, postStart) {
368  pout << cmd << endl;
369  }
370 }
371 
374  _ui->progressBar->hide();
375 #ifdef USE_ASSISTANT
376  _ui->buttonBox->setStandardButtons(
377  QDialogButtonBox::Close|QDialogButtonBox::Help);
378 #else
379  _ui->buttonBox->setStandardButtons(QDialogButtonBox::Close);
380 #endif
381 }
382 
383 void LogDialog::on_proc_error(QProcess::ProcessError) {
385 
386  QString errorType;
387  switch(_proc->error()) {
388  case QProcess::FailedToStart:
389  errorType = tr("process failed to start");
390  break;
391  case QProcess::Crashed:
392  errorType = tr("process crashed after start");
393  break;
394  case QProcess::Timedout:
395  errorType = tr("process timeout");
396  break;
397  case QProcess::WriteError:
398  errorType = tr("write error");
399  break;
400  case QProcess::ReadError:
401  errorType = tr("read error");
402  break;
403  default:
404  errorType = tr("unknown error");
405  break;
406  }
407 
408  printStatus(QString("<br><span class=\"error\">%1</span> %2<br>")
409  .arg(tr("Error during process execution:"))
410  .arg(errorType));
411 }
412 
413 void LogDialog::printStatus(QString msg) {
414  _ui->infoDisplay->setVisible(true);
415  _ui->infoDisplay->insertHtml(msg);
416 }
417 
418 void LogDialog::on_checkDD_toggled(bool checked) {
419  if (!_logProx) {
420  return;
421  }
422  if (checked) {
423  _logProx->setFilterRegExp(QString());
424  }
425  else {
426  _logProx->setFilterRegExp(LogViewProxyModel::debugFilterRegex());
427  }
428  if (_ui->checkScroll->isChecked()) {
429  _ui->logView->scrollToBottom();
430  }
431 }
432 
434  QString selFilter;
435  QString fName = QFileDialog::getSaveFileName(
436  this,tr("Save Log File"),_decorator->filenameHint(),
437  tr("Text File (*.txt *.log);;Html File (*.html *.htm)"),
438  &selFilter);
439  if (fName.isEmpty())
440  return;
441  if (selFilter.contains(".txt")) {
442  QMutexLocker mLock(_logMutex);
443  _logFile->close();
444  _logFile->copy(fName);
445  _logFile->open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text);
446  mLock.unlock();
447  }
448  else if (selFilter.contains(".html")) {
449  saveSettings();
450  _ui->checkDD->setChecked(true);
451  _ui->sBufSize->setValue(0);
452  _ui->logView->selectAll();
453  QMimeData* content = _ui->logView->getSelectedContent();
454  Q_ASSERT(content->hasHtml());
455  QFile out(fName);
456  if (out.open(
457  QIODevice::WriteOnly|QIODevice::Truncate|QIODevice::Text)) {
458  QTextStream ostrm(&out);
459  ostrm << content->html() << endl;
460  }
461  out.close();
462  delete content;
463  _ui->logView->clearSelection();
464  loadSettings();
465  }
466  else {
467  qDebug("Invalid filter selected: %s",
468  selFilter.toLocal8Bit().constData());
469  }
470 }
471 
473  reprint();
474 }
475 
476 void LogDialog::searchLog(QString flt, int offset, bool up) {
477  if (flt.isEmpty()) {
478  return;
479  }
480  QRegExp sWildExp(flt,Qt::CaseInsensitive, QRegExp::WildcardUnix);
481  QItemSelectionModel* selection = _ui->logView->selectionModel();
482  const QAbstractItemModel* model = selection->model();
483  if (model->rowCount() <= 0) {
484  return;
485  }
486  int rowStart = up ? model->rowCount()-1 : 0;
487  if (selection->currentIndex().isValid()) {
488  rowStart = selection->currentIndex().row() + offset;
489  rowStart = qMin(rowStart,model->rowCount()-1);
490  rowStart = qMax(rowStart,0);
491  }
492  int row; QModelIndex ind;
493  for (int ii=0; ii < model->rowCount(); ++ii) {
494  row = ((up?rowStart-ii:rowStart+ii)+model->rowCount())
495  % model->rowCount();
496  ind = model->index(row,0);
497  if (model->data(ind,Qt::DisplayRole).toString().contains(sWildExp)) {
498  selection->setCurrentIndex(
499  ind,QItemSelectionModel::SelectCurrent);
500  _ui->logView->setSelectionModel(selection);
501  _ui->logView->scrollTo(ind,QAbstractItemView::PositionAtCenter);
502  break;
503  }
504  }
505 }
506 
508  searchLog(flt);
509 }
510 
512  searchLog(_ui->eFilter->text(),1,false);
513 }
514 
516  searchLog(_ui->eFilter->text(),-1,true);
517 }
518 
520  emit helpRequested(QString("tuchulcha-run.html%1")
521  .arg(_decorator->helpAnchor()));
522 }
virtual QString logFileName() const =0
logfile name for output logging
void saveSettings()
store config to settings
Definition: LogDialog.cpp:186
void on_checkDD_toggled(bool)
handle debug checkbox
Definition: LogDialog.cpp:418
void on_proc_finished(int=0)
setup close button and hide progress bar
Definition: LogDialog.cpp:372
void on_proc_error(QProcess::ProcessError)
handle errors running the process
Definition: LogDialog.cpp:383
log decorator base class to handle different kinds of log dialogs
Definition: LogDecorators.h:40
QFile * _logFile
log content output
Definition: LogDialog.h:132
virtual QString title() const
title string
virtual void processLine(QString line)
decorator hook to allow log line parsing
void reprint()
reparse log output
Definition: LogDialog.cpp:321
bool _errorLinesDetected
true if log contains error lines
Definition: LogDialog.h:134
Ui::LogDialog * _ui
designer ui
Definition: LogDialog.h:130
Declaration of class LogViewProxyModel.
void printStatus(QString msg)
print status message
Definition: LogDialog.cpp:413
virtual QString desc() const
description string
Declaration of classes in the LogDecorators namespace.
virtual void finishProcessing()
inform about finished processing
Declaration of class LogDialog.
void searchLog(QString filter, int offset=0, bool up=false)
search for given string
Definition: LogDialog.cpp:476
void terminate(bool force=false)
terminate process
Definition: LogDialog.cpp:214
void loadSettings()
load config from settings
Definition: LogDialog.cpp:173
bool hasErrorLines()
query for possible error lines in logfile
Definition: LogDialog.cpp:317
void on_bSearchUp_clicked()
search up
Definition: LogDialog.cpp:515
static QRegExp debugFilterRegex()
regexp for filtering out debug lines
void on_bSearchDown_clicked()
search down
Definition: LogDialog.cpp:511
virtual bool ready(QWidget *parent) const
check if process may be started
void on_proc_readyReadStandardError()
update content by querying process (stderr)
Definition: LogDialog.cpp:281
QStringListModel * _log
log model
Definition: LogDialog.h:126
void helpRequested(QString)
request help on specified page
virtual QStringList postStartCommands(QWidget *parent) const
commands sent to the proccess after start
QProcess * _proc
tuchulcha-run process
Definition: LogDialog.h:131
virtual QString filenameHint() const
hint for filename on save dialog
virtual QWidget * statusWidget()
custom status widget
virtual QString helpAnchor()
html anchor on help page
void kill(bool force=false)
kill process
Definition: LogDialog.cpp:227
void on_sBufSize_valueChanged(int)
handle buf size changes
Definition: LogDialog.cpp:472
QMutex * _logMutex
avoid parallel writes to log window
Definition: LogDialog.h:133
void on_eFilter_textEdited(QString)
handle search filter changes
Definition: LogDialog.cpp:507
void on_proc_readyReadStandardOutput()
update content by querying process (stdout)
Definition: LogDialog.cpp:247
virtual void done(int r)
handle process termination
Definition: LogDialog.cpp:195
void on_buttonBox_helpRequested()
handle help request
Definition: LogDialog.cpp:519
LogDialog(LogDecorators::Decorator *decorator, QWidget *parent=0, Qt::WindowFlags f=0)
constructor
Definition: LogDialog.cpp:43
void on_bSaveLog_clicked()
save logfile
Definition: LogDialog.cpp:433
LogDecorators::Decorator * _decorator
decorator implementation
Definition: LogDialog.h:129
void on_proc_started()
setup abort button and show progress bar
Definition: LogDialog.cpp:356
bool debugOutput
debug output mode
Definition: LogDecorators.h:64
bool waitForFinished(int msecs=1500)
wait for process to finish
Definition: LogDialog.cpp:237
LogViewProxyModel * _logProx
log model proxy
Definition: LogDialog.h:127
virtual QStringList arguments() const =0
determine command line arguments
class for highlighting and handling filtering of log output