pqServerLauncher.cxx 28.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
/*=========================================================================

   Program: ParaView
   Module:    $RCSfile$

   Copyright (c) 2005,2006 Sandia Corporation, Kitware Inc.
   All rights reserved.

   ParaView is a free software; you can redistribute it and/or modify it
Kitware Robot's avatar
Kitware Robot committed
10
   under the terms of the ParaView license version 1.2.
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

   See License_v1.2.txt for the full ParaView license.
   A copy of this license can be obtained by contacting
   Kitware Inc.
   28 Corporate Drive
   Clifton Park, NY 12065
   USA

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

========================================================================*/
#include "pqServerLauncher.h"
#include "ui_pqServerLauncherDialog.h"

#include "pqApplicationCore.h"
#include "pqCoreUtilities.h"
#include "pqFileChooserWidget.h"
#include "pqObjectBuilder.h"
#include "pqServer.h"
40
#include "pqServerConfiguration.h"
41
42
43
44
45
46
#include "pqServerResource.h"
#include "pqSettings.h"
#include "vtkMath.h"
#include "vtkPVConfig.h"
#include "vtkPVOptions.h"
#include "vtkPVXMLElement.h"
47
#include "vtkProcessModule.h"
48
49
50
51
52
53
54
55
#include "vtkTimerLog.h"

#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFormLayout>
56
#include <QHostInfo>
57
58
59
60
61
62
63
64
#include <QLabel>
#include <QLineEdit>
#include <QPointer>
#include <QProcess>
#include <QProcessEnvironment>
#include <QPushButton>
#include <QSpinBox>
#include <QTimer>
65
#include <QtDebug>
66

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//----------------------------------------------------------------------------
const QMetaObject* pqServerLauncher::DefaultServerLauncherType = NULL;
const QMetaObject* pqServerLauncher::setServerDefaultLauncherType(const QMetaObject* other)
{
  const QMetaObject* old = pqServerLauncher::DefaultServerLauncherType;
  pqServerLauncher::DefaultServerLauncherType = other;
  return old;
}

const QMetaObject* pqServerLauncher::defaultServerLauncherType()
{
  return pqServerLauncher::DefaultServerLauncherType;
}

pqServerLauncher* pqServerLauncher::newInstance(
  const pqServerConfiguration& configuration, QObject* parentObject)
{
  if (pqServerLauncher::DefaultServerLauncherType)
Kitware Robot's avatar
Kitware Robot committed
85
  {
86
    QObject* aObject = pqServerLauncher::DefaultServerLauncherType->newInstance(
Kitware Robot's avatar
Kitware Robot committed
87
      Q_ARG(const pqServerConfiguration&, configuration), Q_ARG(QObject*, parentObject));
88
    if (pqServerLauncher* aLauncher = qobject_cast<pqServerLauncher*>(aObject))
Kitware Robot's avatar
Kitware Robot committed
89
    {
90
91
      return aLauncher;
    }
Kitware Robot's avatar
Kitware Robot committed
92
93
    delete aObject;
  }
94
95
96
97
  return new pqServerLauncher(configuration, parentObject);
}

//----------------------------------------------------------------------------
98
99
namespace
{
Kitware Robot's avatar
Kitware Robot committed
100
101
102
103
104
/// pqWidget is used to make it easier to get and set values from different
/// types of widgets.
class pqWidget : public QObject
{
  QString PropertyName;
105

Kitware Robot's avatar
Kitware Robot committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
public:
  QWidget* Widget;
  bool ToSave;
  pqWidget()
    : Widget(NULL)
    , ToSave(false)
  {
  }
  pqWidget(QWidget* wdg, const QString& pname)
    : PropertyName(pname)
    , Widget(wdg)
    , ToSave(false)
  {
  }
  virtual ~pqWidget() {}

  virtual QVariant get() const
  {
    return this->Widget->property(this->PropertyName.toLatin1().data());
  }
  virtual void set(const QVariant& value)
  {
    this->Widget->setProperty(this->PropertyName.toLatin1().data(), value);
  }

private:
  Q_DISABLE_COPY(pqWidget)
};
134

Kitware Robot's avatar
Kitware Robot committed
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class pqWidgetForComboBox : public pqWidget
{
public:
  pqWidgetForComboBox(QComboBox* widget)
    : pqWidget(widget, QString())
  {
  }

  virtual QVariant get() const
  {
    QComboBox* combobox = qobject_cast<QComboBox*>(this->Widget);
    return combobox->itemData(combobox->currentIndex());
  }

  virtual void set(const QVariant& value)
  {
    QComboBox* combobox = qobject_cast<QComboBox*>(this->Widget);
    combobox->setCurrentIndex(combobox->findData(value));
  }

private:
  Q_DISABLE_COPY(pqWidgetForComboBox)
};
158

Kitware Robot's avatar
Kitware Robot committed
159
160
161
162
class pqWidgetForCheckbox : public pqWidget
{
  QString TrueValue;
  QString FalseValue;
163

Kitware Robot's avatar
Kitware Robot committed
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
public:
  pqWidgetForCheckbox(QCheckBox* widget, const char* tval, const char* fval)
    : pqWidget(widget, QString())
    , TrueValue(tval)
    , FalseValue(fval)
  {
  }

  virtual QVariant get() const
  {
    QCheckBox* checkbox = qobject_cast<QCheckBox*>(this->Widget);
    return checkbox->isChecked() ? this->TrueValue : this->FalseValue;
  }

  virtual void set(const QVariant& value)
  {
    QCheckBox* checkbox = qobject_cast<QCheckBox*>(this->Widget);
    checkbox->setChecked(value.toString() == this->TrueValue);
  }

private:
  Q_DISABLE_COPY(pqWidgetForCheckbox)
};
187

Kitware Robot's avatar
Kitware Robot committed
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/// Returns pre-defined run-time environment. This includes the environement
/// of this application itself as well as some predefined values.
QProcessEnvironment getDefaultEnvironment(const pqServerConfiguration& configuration)
{
  pqServerResource resource = configuration.resource();

  // Get the process environment.
  QProcessEnvironment options = QProcessEnvironment::systemEnvironment();

  // Now append the pre-defined runtime environment to this.
  options.insert("PV_CLIENT_HOST", QHostInfo::localHostName());
  options.insert("PV_CONNECTION_URI", resource.toURI());
  options.insert("PV_CONNECTION_SCHEME", resource.scheme());
  options.insert("PV_VERSION_MAJOR", QString::number(PARAVIEW_VERSION_MAJOR));
  options.insert("PV_VERSION_MINOR", QString::number(PARAVIEW_VERSION_MINOR));
  options.insert("PV_VERSION_PATCH", QString::number(PARAVIEW_VERSION_PATCH));
  options.insert("PV_VERSION", PARAVIEW_VERSION);
  options.insert("PV_VERSION_FULL", PARAVIEW_VERSION_FULL);
  options.insert("PV_SERVER_HOST", resource.host());
  options.insert("PV_SERVER_PORT", QString::number(resource.port(11111)));
  options.insert("PV_DATA_SERVER_HOST", resource.dataServerHost());
  options.insert("PV_DATA_SERVER_PORT", QString::number(resource.dataServerPort(11111)));
  options.insert("PV_RENDER_SERVER_HOST", resource.renderServerHost());
  options.insert("PV_RENDER_SERVER_PORT", QString::number(resource.renderServerPort(22221)));

#if defined(_WIN32)
  options.insert("PV_CLIENT_PLATFORM", "Windows");
#elif defined(__APPLE__)
  options.insert("PV_CLIENT_PLATFORM", "Apple");
#elif defined(__linux__)
  options.insert("PV_CLIENT_PLATFORM", "Linux");
#elif defined(__unix__)
  options.insert("PV_CLIENT_PLATFORM", "Unix");
#else
  options.insert("PV_CLIENT_PLATFORM", "Unknown");
#endif

  return options;
}

/// Processes the <Options /> XML defined in the server configuration to
/// update the dialog with widgets of right type with correct default values.
/// It uses application settings to obtain the default values, whenever
/// possible.
bool createWidgets(QMap<QString, pqWidget*>& widgets, QDialog& dialog,
  const pqServerConfiguration& configuration, QProcessEnvironment& options)
{
  vtkPVXMLElement* optionsXML = configuration.optionsXML();
  Q_ASSERT(optionsXML != NULL);

  QFormLayout* formLayout = new QFormLayout();
  dialog.setLayout(formLayout);
  dialog.setWindowTitle(QString("Connection Options for \"%1\"").arg(configuration.name()));

  pqSettings* settings = pqApplicationCore::instance()->settings();

  // Process the <Options/> to create dialog with widgets.
  for (unsigned int cc = 0; cc < optionsXML->GetNumberOfNestedElements(); cc++)
  {
    vtkPVXMLElement* node = optionsXML->GetNestedElement(cc);
    if (node->GetName() == NULL)
249
    {
Kitware Robot's avatar
Kitware Robot committed
250
      continue;
251
252
    }

Kitware Robot's avatar
Kitware Robot committed
253
254
255
256
257
    if (strcmp(node->GetName(), "Set") == 0)
    {
      options.insert(node->GetAttribute("name"), node->GetAttribute("value"));
    }
    else if (strcmp(node->GetName(), "Option") == 0)
258
    {
Kitware Robot's avatar
Kitware Robot committed
259
260
261
262
263
      vtkPVXMLElement* typeNode = node->GetNestedElement(0);
      if (typeNode == NULL || typeNode->GetName() == NULL)
      {
        continue;
      }
264

Kitware Robot's avatar
Kitware Robot committed
265
266
267
268
      const char* name = node->GetAttribute("name");
      const char* label = node->GetAttributeOrDefault("label", name);
      bool readonly = strcmp(node->GetAttributeOrDefault("readonly", "false"), "true") == 0;
      bool save = strcmp(node->GetAttributeOrDefault("save", "true"), "true") == 0;
269

Kitware Robot's avatar
Kitware Robot committed
270
      QString settingsKey = QString("SERVER_STARTUP/%1.%2").arg(configuration.name()).arg(name);
271

Kitware Robot's avatar
Kitware Robot committed
272
273
274
      bool default_is_random = false;
      QVariant default_value;
      if (typeNode->GetAttribute("default"))
275
      {
Kitware Robot's avatar
Kitware Robot committed
276
277
278
279
280
        default_is_random = (strcmp(typeNode->GetAttribute("default"), "random") == 0);
        default_value = QString(typeNode->GetAttribute("default"));

        // if default_is_random, save cannot be true.
        if (default_is_random)
281
        {
Kitware Robot's avatar
Kitware Robot committed
282
          save = false;
283
        }
Kitware Robot's avatar
Kitware Robot committed
284
      }
285

Kitware Robot's avatar
Kitware Robot committed
286
287
288
289
290
291
292
293
294
295
296
      // noise is a in the range [0, 1].
      double noise = 0.0;
      if (default_is_random)
      {
        // We need a seed that changes every execution. Get the
        // universal time as double and then add all the bytes
        // together to get a nice seed without causing any overflow.
        long rseed = 0;
        double atime = vtkTimerLog::GetUniversalTime() * 1000;
        char* tc = (char*)&atime;
        for (unsigned int ic = 0; ic < sizeof(double); ic++)
297
        {
Kitware Robot's avatar
Kitware Robot committed
298
          rseed += tc[ic];
299
        }
Kitware Robot's avatar
Kitware Robot committed
300
301
302
        vtkMath::RandomSeed(rseed);
        noise = vtkMath::Random();
      }
303

Kitware Robot's avatar
Kitware Robot committed
304
305
306
307
308
      // obtain default value from settings if available.
      if (save && settings->contains(settingsKey))
      {
        default_value = settings->value(settingsKey);
      }
309

Kitware Robot's avatar
Kitware Robot committed
310
311
312
313
314
315
316
317
318
      if (strcmp(typeNode->GetName(), "Range") == 0)
      {
        QString min = typeNode->GetAttributeOrDefault("min", "0");
        QString max = typeNode->GetAttributeOrDefault("max", "99999999999");
        QString step = typeNode->GetAttributeOrDefault("step", "1");
        QWidget* widget = NULL;
        if (strcmp(typeNode->GetAttributeOrDefault("type", "int"), "int") == 0)
        {
          widget = new QSpinBox(&dialog);
319
320
          if (default_is_random)
          {
Kitware Robot's avatar
Kitware Robot committed
321
            default_value = min.toInt() + (max.toInt() - min.toInt()) * noise;
322
          }
Kitware Robot's avatar
Kitware Robot committed
323
324
325
326
327
        }
        else // assume double.
        {
          widget = new QDoubleSpinBox(&dialog);
          if (default_is_random)
328
          {
Kitware Robot's avatar
Kitware Robot committed
329
            default_value = min.toDouble() + (max.toDouble() - min.toDouble()) * noise;
330
          }
Kitware Robot's avatar
Kitware Robot committed
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
        }
        widgets[name] = new pqWidget(widget, "value");
        widget->setProperty("minimum", QVariant(min));
        widget->setProperty("maximum", QVariant(max));
        widget->setProperty("singleStep", QVariant(step));
      }
      else if (strcmp(typeNode->GetName(), "String") == 0)
      {
        QLineEdit* widget = new QLineEdit(QString(), &dialog);
        widgets[name] = new pqWidget(widget, "text");
      }
      else if (strcmp(typeNode->GetName(), "File") == 0)
      {
        pqFileChooserWidget* widget = new pqFileChooserWidget(&dialog);
        widget->setForceSingleFile(true);
        widgets[name] = new pqWidget(widget, "singleFilename");
      }
      else if (strcmp(typeNode->GetName(), "Boolean") == 0)
      {
        QCheckBox* checkbox = new QCheckBox(&dialog);
        const char* true_value = typeNode->GetAttributeOrDefault("true", "1");
        const char* false_value = typeNode->GetAttributeOrDefault("false", "0");
        widgets[name] = new pqWidgetForCheckbox(checkbox, true_value, false_value);
      }
      else if (strcmp(typeNode->GetName(), "Enumeration") == 0)
      {
        QComboBox* widget = new QComboBox(&dialog);
        for (unsigned int kk = 0; kk < typeNode->GetNumberOfNestedElements(); kk++)
        {
          vtkPVXMLElement* child = typeNode->GetNestedElement(kk);
          if (QString(child->GetName()) == "Entry")
362
          {
Kitware Robot's avatar
Kitware Robot committed
363
364
365
            QString xml_value = child->GetAttribute("value");
            QString xml_label = child->GetAttributeOrDefault("label", xml_value.toLatin1().data());
            widget->addItem(xml_label, xml_value);
366
367
          }
        }
Kitware Robot's avatar
Kitware Robot committed
368
369
        widgets[name] = new pqWidgetForComboBox(widget);
      }
370
      else
Kitware Robot's avatar
Kitware Robot committed
371
372
373
374
      {
        qWarning() << "Ignoring unknown element '<" << typeNode->GetName()
                   << "/>' discovered under <Option/> element.";
        continue;
375
      }
Kitware Robot's avatar
Kitware Robot committed
376
377
378
379
380
381
382
383
384
385
      widgets[name]->setParent(&dialog);
      widgets[name]->ToSave = save;
      widgets[name]->set(default_value);
      widgets[name]->Widget->setEnabled(!readonly);
      widgets[name]->Widget->setObjectName(name);
      formLayout->addRow(label, widgets[name]->Widget);
    } // end of <Option />
    else if (strcmp(node->GetName(), "Switch") == 0)
    {
      continue; // Switch's are handled afterwords
386
    }
Kitware Robot's avatar
Kitware Robot committed
387
388
389
390
391
392
393
394
395
396
397
398
399
400
    else
    {
      qWarning() << "Ignoring unknown element '<" << node->GetName()
                 << "/>' discovered under <Options/> element.";
    }
  }

  QDialogButtonBox* buttonBox =
    new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
  QObject::connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
  QObject::connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
  formLayout->addRow(buttonBox);
  return true;
}
401

Kitware Robot's avatar
Kitware Robot committed
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/// Update the QProcessEnvironment based on the values picked by the user on
/// the widgets. This function may change the resource uri for the \c
/// configuration itself if any of the GUI widgets changed the server-port
/// numbers.
void updateEnvironment(const QMap<QString, pqWidget*>& widgets,
  pqServerConfiguration& configuration, QProcessEnvironment& options)
{
  pqSettings* settings = pqApplicationCore::instance()->settings();
  pqServerResource resource = configuration.resource();
  foreach (const pqWidget* item, widgets)
  {
    QString name = item->Widget->objectName();
    QVariant chosen_value = item->get();
    if (item->ToSave)
416
    {
Kitware Robot's avatar
Kitware Robot committed
417
418
419
420
421
      // save the chosen value in settings if requested.
      QString settingsKey = QString("SERVER_STARTUP/%1.%2").arg(configuration.name()).arg(name);
      settings->setValue(settingsKey, chosen_value);
    }
    options.insert(name, chosen_value.toString());
422

Kitware Robot's avatar
Kitware Robot committed
423
424
425
426
427
428
429
    // Some options can affect the server resource itself e.g. PV_SERVER_PORT etc.
    // So if those were changed using the config XML, we need to update the
    // resource.
    if (name == "PV_SERVER_PORT")
    {
      resource.setPort(chosen_value.toInt());
      configuration.setResource(resource);
430
    }
Kitware Robot's avatar
Kitware Robot committed
431
432
433
434
435
436
437
438
439
440
441
442
    else if (name == "PV_DATA_SERVER_PORT")
    {
      resource.setDataServerPort(chosen_value.toInt());
      configuration.setResource(resource);
    }
    else if (name == "PV_RENDER_SERVER_PORT")
    {
      resource.setRenderServerPort(chosen_value.toInt());
      configuration.setResource(resource);
    }
  }
}
443

Kitware Robot's avatar
Kitware Robot committed
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
/// Process <Switch />
void handleSwitchCases(const pqServerConfiguration& configuration, QProcessEnvironment& options)
{
  vtkPVXMLElement* optionsXML = configuration.optionsXML();
  for (unsigned int cc = 0; cc < optionsXML->GetNumberOfNestedElements(); cc++)
  {
    vtkPVXMLElement* switchXML = optionsXML->GetNestedElement(cc);
    if (!switchXML->GetName() || strcmp(switchXML->GetName(), "Switch") != 0)
    {
      continue;
    }
    const char* variable = switchXML->GetAttribute("name");
    if (!variable)
    {
      qWarning("Missing attribute 'name' in 'Switch' statement");
      continue;
    }
    if (!options.contains(variable))
462
    {
Kitware Robot's avatar
Kitware Robot committed
463
464
465
466
467
468
469
470
471
472
      qWarning() << "'Switch' statement has no effect since no variable named " << variable
                 << " is defined. ";
      continue;
    }
    QString value = options.value(variable);
    bool handled = false;
    for (unsigned int kk = 0; !handled && kk < switchXML->GetNumberOfNestedElements(); kk++)
    {
      vtkPVXMLElement* caseXML = switchXML->GetNestedElement(kk);
      if (!caseXML->GetName() || strcmp(caseXML->GetName(), "Case") != 0)
473
      {
Kitware Robot's avatar
Kitware Robot committed
474
        qWarning() << "'<Switch/> element can only contain <Case/> elements";
475
        continue;
Kitware Robot's avatar
Kitware Robot committed
476
      }
477
    
Kitware Robot's avatar
Kitware Robot committed
478
      const char* case_value = caseXML->GetAttribute("value");
479
480
481

      // Case: Explicitly does not match
      if (case_value && value != case_value)
Kitware Robot's avatar
Kitware Robot committed
482
      {
483
        continue;
Kitware Robot's avatar
Kitware Robot committed
484
      }
485
486

      // Case: Either matches or is default, i.e. no value
Kitware Robot's avatar
Kitware Robot committed
487
488
489
490
491
      handled = true;
      for (unsigned int i = 0; i < caseXML->GetNumberOfNestedElements(); i++)
      {
        vtkPVXMLElement* setXML = caseXML->GetNestedElement(i);
        if (QString(setXML->GetName()) == "Set")
492
        {
Kitware Robot's avatar
Kitware Robot committed
493
494
495
          const char* option_name = setXML->GetAttributeOrDefault("name", "");
          const char* option_value = setXML->GetAttributeOrDefault("value", "");
          options.insert(option_name, option_value);
496
        }
Kitware Robot's avatar
Kitware Robot committed
497
        else
498
        {
Kitware Robot's avatar
Kitware Robot committed
499
500
          qWarning() << "'Case' element can only contain 'Set' elements as children and not '"
                     << setXML->GetName() << "'";
501
502
        }
      }
503
504
505
506
507
508
509
510
511
512
513

      // Case: Default needs early exit; warn if not the last case
      if(!case_value)
      {
        if(kk+1 < switchXML->GetNumberOfNestedElements())
        {
          qWarning() << "Default 'Case' possibly overshadows explicit 'Case' "
                     << "statements for variable '" << variable << "'";
        }
        break;
      }
514
    }
Kitware Robot's avatar
Kitware Robot committed
515
516
517
518
519
520
521
522
    if (!handled)
    {
      qWarning() << "Case '" << value << "' not handled in 'Switch' for variable "
                                         "'"
                 << variable << "'";
    }
  }
}
523
524
}

525
526
527
528
529
530
531
532
533
class pqServerLauncher::pqInternals
{
public:
  pqServerConfiguration Configuration;
  QProcessEnvironment Options;
  QPointer<pqServer> Server;
  QMap<QString, pqWidget*> ActiveWidgets; // map to save the widgets in promptOptions().
};

534
535
//-----------------------------------------------------------------------------
pqServerLauncher::pqServerLauncher(
Kitware Robot's avatar
Kitware Robot committed
536
  const pqServerConfiguration& _configuration, QObject* parentObject)
537
538
539
540
541
  : Superclass(parentObject)
{
  this->Internals = new pqInternals();

  // we create a clone so that we can change the values in place.
542
  this->Internals->Configuration = _configuration.clone();
543
544
545
546
547
548
549
550
551
}

//-----------------------------------------------------------------------------
pqServerLauncher::~pqServerLauncher()
{
  delete this->Internals;
  this->Internals = NULL;
}

552
553
554
555
556
557
//-----------------------------------------------------------------------------
pqServerConfiguration& pqServerLauncher::configuration() const
{
  return this->Internals->Configuration;
}

558
559
560
//-----------------------------------------------------------------------------
bool pqServerLauncher::connectToServer()
{
Kitware Robot's avatar
Kitware Robot committed
561
562
563
  pqServerConfiguration::StartupType startupType = this->Internals->Configuration.startupType();
  if (startupType != pqServerConfiguration::MANUAL && startupType != pqServerConfiguration::COMMAND)
  {
564
    qCritical() << "Invalid server configuration."
Kitware Robot's avatar
Kitware Robot committed
565
                << "Cannot connect to server";
566
    return false;
Kitware Robot's avatar
Kitware Robot committed
567
  }
568

569
570
571
572
  // Check if there are any user-configurable parameters that we should obtain
  // from the user. promptOptions() returns false only when user hits cancel, in
  // which case the user is aborting connecting to the server.
  if (!this->promptOptions())
Kitware Robot's avatar
Kitware Robot committed
573
  {
574
    return false;
Kitware Robot's avatar
Kitware Robot committed
575
  }
576
577

  if (startupType == pqServerConfiguration::COMMAND)
Kitware Robot's avatar
Kitware Robot committed
578
  {
579
    if (this->isReverseConnection())
Kitware Robot's avatar
Kitware Robot committed
580
    {
581
582
583
584
      // in reverse connection, we don't launchServer() immediately, instead we
      // wait for the client to setup the "socket" before starting the server
      // process.
      QTimer::singleShot(0, this, SLOT(launchServerForReverseConnection()));
Kitware Robot's avatar
Kitware Robot committed
585
    }
586
    else
Kitware Robot's avatar
Kitware Robot committed
587
    {
588
      if (!this->launchServer(true))
Kitware Robot's avatar
Kitware Robot committed
589
      {
590
591
592
        return false;
      }
    }
Kitware Robot's avatar
Kitware Robot committed
593
  }
594
595

  return this->connectToPrelaunchedServer();
596
597
598
599
600
601
602
603
}

//-----------------------------------------------------------------------------
bool pqServerLauncher::connectToPrelaunchedServer()
{
  pqObjectBuilder* builder = pqApplicationCore::instance()->getObjectBuilder();

  QDialog dialog(pqCoreUtilities::mainWidget());
Kitware Robot's avatar
Kitware Robot committed
604
  QObject::connect(&dialog, SIGNAL(rejected()), builder, SLOT(abortPendingConnections()));
605
606
607

  Ui::pqServerLauncherDialog ui;
  ui.setupUi(&dialog);
608
  ui.message->setText(QString("Establishing connection to '%1' \n"
Kitware Robot's avatar
Kitware Robot committed
609
610
                              "Waiting for server to connect.")
                        .arg(this->Internals->Configuration.name()));
611
612
  dialog.setWindowTitle("Waiting for Server Connection");
  if (this->isReverseConnection())
Kitware Robot's avatar
Kitware Robot committed
613
  {
614
615
616
617
    // using reverse connect, popup the dialog.
    dialog.show();
    dialog.raise();
    dialog.activateWindow();
Kitware Robot's avatar
Kitware Robot committed
618
  }
619

620
  const pqServerResource& resource = this->Internals->Configuration.resource();
621
622
623
624
  this->Internals->Server = builder->createServer(resource);
  return this->Internals->Server != NULL;
}

625
626
627
628
629
630
631
//-----------------------------------------------------------------------------
bool pqServerLauncher::isReverseConnection() const
{
  const pqServerResource& resource = this->Internals->Configuration.resource();
  return (resource.scheme() == "csrc" || resource.scheme() == "cdsrsrc");
}

632
633
634
635
//-----------------------------------------------------------------------------
bool pqServerLauncher::promptOptions()
{
  vtkPVXMLElement* optionsXML = this->Internals->Configuration.optionsXML();
636
637
638
639
  // Get the process environment.
  QProcessEnvironment& options = this->Internals->Options;
  // setup the options using the default environment, in any case.
  options = getDefaultEnvironment(this->Internals->Configuration);
640
  if (optionsXML == NULL)
Kitware Robot's avatar
Kitware Robot committed
641
  {
642
    return true;
Kitware Robot's avatar
Kitware Robot committed
643
  }
644
645
646
647

  QDialog dialog(pqCoreUtilities::mainWidget());

  // setup the dialog using the configuration's XML.
Kitware Robot's avatar
Kitware Robot committed
648
649
  QMap<QString, pqWidget*>& widgets = this->Internals->ActiveWidgets;
  ; // map to save the widgets.
650
651
  // note: all pqWidget instances created are set with parent as the dialog, so
  // we don't need to clean them up explicitly.
652
  createWidgets(widgets, dialog, this->Internals->Configuration, options);
653
654
  // give subclasses an opportunity to fine-tune the dialog.
  this->prepareDialogForPromptOptions(dialog);
655
  if (dialog.exec() != QDialog::Accepted)
Kitware Robot's avatar
Kitware Robot committed
656
  {
657
    widgets.clear();
658
    return false;
Kitware Robot's avatar
Kitware Robot committed
659
  }
660

661
  this->updateOptionsUsingUserSelections();
662

663
  // if options contains PV_CONNECT_ID. We need to update the pqOptions to
664
  // give it the correct connection-id.
665
  if (options.contains("PV_CONNECT_ID"))
Kitware Robot's avatar
Kitware Robot committed
666
667
  {
    vtkPVOptions* pvoptions = vtkProcessModule::GetProcessModule()->GetOptions();
668
    if (pvoptions)
Kitware Robot's avatar
Kitware Robot committed
669
    {
670
      pvoptions->SetConnectID(options.value("PV_CONNECT_ID").toInt());
671
    }
Kitware Robot's avatar
Kitware Robot committed
672
  }
673

674
  widgets.clear();
675
676
677
678
  // Now we have the environment filled up correctly.
  return true;
}

679
680
681
682
//-----------------------------------------------------------------------------
void pqServerLauncher::updateOptionsUsingUserSelections()
{
  if (this->Internals->ActiveWidgets.size() > 0)
Kitware Robot's avatar
Kitware Robot committed
683
  {
684
    /// now based on user-chosen values, update the options.
Kitware Robot's avatar
Kitware Robot committed
685
686
    updateEnvironment(
      this->Internals->ActiveWidgets, this->Internals->Configuration, this->Internals->Options);
687
688
689
690
691

    // Now that user entered options have been processes, handle the <Switch />
    // elements.  This has to happen after the Options have been updated with
    // user-selected values so that we can pick the right case.
    handleSwitchCases(this->Internals->Configuration, this->Internals->Options);
Kitware Robot's avatar
Kitware Robot committed
692
  }
693
694
}

695
//-----------------------------------------------------------------------------
696
697
698
void pqServerLauncher::launchServerForReverseConnection()
{
  if (!this->launchServer(false))
Kitware Robot's avatar
Kitware Robot committed
699
  {
700
701
702
703
    // server-launch failed, abort the "waiting for the server to connect" part
    // by letting the pqObjectBuilder know.
    pqObjectBuilder* builder = pqApplicationCore::instance()->getObjectBuilder();
    builder->abortPendingConnections();
Kitware Robot's avatar
Kitware Robot committed
704
  }
705
706
707
708
}

//-----------------------------------------------------------------------------
bool pqServerLauncher::launchServer(bool show_status_dialog)
709
710
711
712
713
{
  // We need launch the server.
  double timeout, delay;
  QString command = this->Internals->Configuration.command(timeout, delay);
  if (command.isEmpty())
Kitware Robot's avatar
Kitware Robot committed
714
  {
715
716
    qCritical() << "Could not determine command to launch the server.";
    return false;
Kitware Robot's avatar
Kitware Robot committed
717
  }
718

719
  // Pop-up a dialog to tell the user that the server is being launched.
720
721
722
723
  QDialog dialog(pqCoreUtilities::mainWidget());
  Ui::pqServerLauncherDialog ui;
  ui.setupUi(&dialog);
  ui.cancel->hide();
Kitware Robot's avatar
Kitware Robot committed
724
  ui.message->setText(QString("Launching server '%1'").arg(this->Internals->Configuration.name()));
725
  if (show_status_dialog)
Kitware Robot's avatar
Kitware Robot committed
726
  {
727
728
729
    dialog.show();
    dialog.raise();
    dialog.activateWindow();
Kitware Robot's avatar
Kitware Robot committed
730
  }
731
732
733
734
735
736

  // replace all $FOO$ with values for QProcessEnvironment.
  QRegExp regex("\\$([^$]*)\\$");

  // Do string-substitution for the command line.
  while (regex.indexIn(command) > -1)
Kitware Robot's avatar
Kitware Robot committed
737
  {
738
739
740
741
    QString before = regex.cap(0);
    QString variable = regex.cap(1);
    QString after = this->Internals->Options.value(variable, variable);
    command.replace(before, after);
Kitware Robot's avatar
Kitware Robot committed
742
  }
743

744
  return this->processCommand(command, timeout, delay, &this->Internals->Options);
745
746
747
}

//-----------------------------------------------------------------------------
Kitware Robot's avatar
Kitware Robot committed
748
749
bool pqServerLauncher::processCommand(
  QString command, double timeout, double delay, const QProcessEnvironment* options)
750
{
751
  QProcess* process = new QProcess(pqApplicationCore::instance());
752

Kitware Robot's avatar
Kitware Robot committed
753
754
  if (options != NULL)
  {
755
    process->setProcessEnvironment(*options);
Kitware Robot's avatar
Kitware Robot committed
756
  }
757

Kitware Robot's avatar
Kitware Robot committed
758
759
760
761
  QObject::connect(process, SIGNAL(error(QProcess::ProcessError)), this,
    SLOT(processFailed(QProcess::ProcessError)));
  QObject::connect(process, SIGNAL(readyReadStandardError()), this, SLOT(readStandardError()));
  QObject::connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStandardOutput()));
762
763
764
765

  process->start(command);

  // wait for process to start.
766
767
  // waitForStarted() may block until the process starts. That is generally a short
  // span of time, hence we don't worry about it too much.
768
  if (process->waitForStarted(timeout > 0. ? static_cast<int>(timeout * 1000.) : -1) == false)
Kitware Robot's avatar
Kitware Robot committed
769
  {
770
    qCritical() << "Command launch timed out.";
771
772
    process->kill();
    delete process;
773
    return false;
Kitware Robot's avatar
Kitware Robot committed
774
  }
775

776
  if (delay == -1)
Kitware Robot's avatar
Kitware Robot committed
777
  {
778
    // Wait for process to be finished
Kitware Robot's avatar
Kitware Robot committed
779
780
    while (process->state() == QProcess::Running)
    {
781
782
      process->waitForFinished(100);
    }
Kitware Robot's avatar
Kitware Robot committed
783
  }
784
  else
Kitware Robot's avatar
Kitware Robot committed
785
  {
786
787
    // wait for delay before attempting to connect to the server.
    pqEventDispatcher::processEventsAndWait(static_cast<int>(delay * 1000));
Kitware Robot's avatar
Kitware Robot committed
788
  }
789
790

  // Check process state
791
  if (process->state() != QProcess::Running)
Kitware Robot's avatar
Kitware Robot committed
792
  {
793
    if (process->exitStatus() != QProcess::NormalExit || process->exitCode() != 0)
Kitware Robot's avatar
Kitware Robot committed
794
    {
795
796
      // if the launched code exited with error, we consider that the process
      // failed. If the process quits with success, we
797
798
      // still assume that the script has launched the server successfully (it's
      // just treated as non-blocking).
Kitware Robot's avatar
Kitware Robot committed
799
      qCritical() << "Command aborted.";
800
801
      process->deleteLater();
      return false;
Kitware Robot's avatar
Kitware Robot committed
802
    }
803
    else
Kitware Robot's avatar
Kitware Robot committed
804
    {
805
806
807
      // process has completed, so delete it.
      process->deleteLater();
    }
Kitware Robot's avatar
Kitware Robot committed
808
  }
809
  else
Kitware Robot's avatar
Kitware Robot committed
810
  {
811
    // setup slot to delete the QProcess instance when the process exits.
Kitware Robot's avatar
Kitware Robot committed
812
813
814
    QObject::connect(
      process, SIGNAL(finished(int, QProcess::ExitStatus)), process, SLOT(deleteLater()));
  }
815
  return true;
816
817
818
819
820
821
}

//-----------------------------------------------------------------------------
void pqServerLauncher::processFailed(QProcess::ProcessError error_code)
{
  switch (error_code)
Kitware Robot's avatar
Kitware Robot committed
822
823
824
825
826
827
828
829
830
831
832
833
834
  {
    case QProcess::FailedToStart:
      qCritical() << "The process failed to start. Either the invoked program is missing, "
                     "or you may have insufficient permissions to invoke the program.";
      break;

    case QProcess::Crashed:
      qCritical() << "The process crashed some time after starting successfully.";
      break;

    default:
      qCritical() << "Process failed with error";
  }
835
836
837
838
839
840
841
}

//-----------------------------------------------------------------------------
pqServer* pqServerLauncher::connectedServer() const
{
  return this->Internals->Server;
}
842
843
844
845
846
847

//-----------------------------------------------------------------------------
void pqServerLauncher::readStandardOutput()
{
  QProcess* process = qobject_cast<QProcess*>(this->sender());
  if (process)
Kitware Robot's avatar
Kitware Robot committed
848
  {
849
    this->handleProcessStandardOutput(process->readAllStandardOutput());
850
    pqEventDispatcher::processEvents();
Kitware Robot's avatar
Kitware Robot committed
851
  }
852
853
854
855
856
857
858
}

//-----------------------------------------------------------------------------
void pqServerLauncher::readStandardError()
{
  QProcess* process = qobject_cast<QProcess*>(this->sender());
  if (process)
Kitware Robot's avatar
Kitware Robot committed
859
  {
860
    this->handleProcessErrorOutput(process->readAllStandardError());
861
    pqEventDispatcher::processEvents();
Kitware Robot's avatar
Kitware Robot committed
862
  }
863
}
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881

//-----------------------------------------------------------------------------
void pqServerLauncher::handleProcessStandardOutput(const QByteArray& data)
{
  qDebug() << data.data();
}

//-----------------------------------------------------------------------------
void pqServerLauncher::handleProcessErrorOutput(const QByteArray& data)
{
  qCritical() << data.data();
}

//-----------------------------------------------------------------------------
QProcessEnvironment& pqServerLauncher::options() const
{
  return this->Internals->Options;
}