Salome HOME
Merge branch 'V7_dev'
[modules/jobmanager.git] / src / genericgui / BL_JobsManager_QT.cxx
1 // Copyright (C) 2009-2016  CEA/DEN, EDF R&D
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include "BL_JobsManager_QT.hxx"
21 #include "BL_GenericGui.hxx"
22
23 #include <QApplication>
24 #include <QFileDialog>
25 #include <QGroupBox>
26 #include <QHBoxLayout>
27 #include <QMenu>
28 #include <QPushButton>
29 #include <QScrollArea>
30 #include <QStandardItemModel>
31 #include <QTextEdit>
32 #include <QTimer>
33 #include <QVBoxLayout>
34 #include <vector>
35
36 using namespace std;
37
38 // To tokenize a string
39 static void Tokenize(const std::string& str, std::vector<std::string>& tokens, const std::string& delimiters = " ");
40
41 BL::JobManagerEvent::JobManagerEvent(const std::string & action_i, 
42                                      const std::string & event_name_i, 
43                                      const std::string & job_name_i, 
44                                      const std::string & data_i) : QEvent(QEvent::User)
45 {
46   action = action_i;
47   event_name = event_name_i;
48   job_name = job_name_i;
49   data = data_i;
50 }
51
52 BL::JobManagerEvent::~JobManagerEvent() {}  
53
54 BL::JobsManager_QT::JobsManager_QT(QWidget * parent, BL::GenericGui * main_gui, BL::SALOMEServices * salome_services) : 
55   QDockWidget(parent), BL::JobsManager(salome_services)
56 {
57   DEBTRACE("Creating BL::JobsManager_QT");
58   _main_gui = main_gui;
59   setObserver(this);
60   _model_manager = NULL;
61
62   // Widget Part
63
64   QWidget * main_widget = new QWidget(this);
65
66   _load_jobs = new QPushButton("Load Jobs");
67   _save_jobs = new QPushButton("Save Jobs");
68   connect(_load_jobs, SIGNAL(clicked()), this, SLOT(load_jobs_button()));
69   connect(_save_jobs, SIGNAL(clicked()), this, SLOT(save_jobs_button()));
70
71   _auto_refresh_jobs = new QPushButton("Auto Refresh: no");
72   _timer = new QTimer(this);
73   _timer->stop();
74   connect(_timer, SIGNAL(timeout()), this, SLOT(RefreshJobs()));
75
76   // Menu for auto refresh
77   QMenu * refresh_menu = new QMenu(this);
78   refresh_menu->addAction("No", this, SLOT(no_auto_refresh()));
79   refresh_menu->addAction("10 seconds", this, SLOT(ten_seconds_refresh()));
80   refresh_menu->addAction("30 seconds", this, SLOT(thirty_seconds_refresh()));
81   refresh_menu->addAction("1 minute", this, SLOT(one_minute_refresh()));
82   refresh_menu->addAction("5 minutes", this, SLOT(five_minutes_refresh()));
83   refresh_menu->addAction("30 minutes", this, SLOT(thirty_minutes_refresh()));
84   refresh_menu->addAction("1 hour", this, SLOT(one_hour_refresh()));
85   _auto_refresh_jobs->setMenu(refresh_menu);
86
87   QHBoxLayout * button_layout = new QHBoxLayout();
88   button_layout->addWidget(_load_jobs);
89   button_layout->addWidget(_save_jobs);
90   button_layout->addWidget(_auto_refresh_jobs);
91
92   QGroupBox * message_box = new QGroupBox("Messages");
93   _log = new QTextEdit(this);
94   _log->setReadOnly(true);
95   QVBoxLayout * message_box_layout = new QVBoxLayout(message_box);
96   message_box_layout->addWidget(_log);
97   message_box->setLayout(message_box_layout);
98
99   QVBoxLayout * mainLayout = new QVBoxLayout();
100   mainLayout->addLayout(button_layout);
101   mainLayout->addWidget(message_box);
102   main_widget->setLayout(mainLayout);
103
104   QScrollArea * scroll_widget = new QScrollArea(this);
105   scroll_widget->setWidget(main_widget);
106   scroll_widget->setWidgetResizable(true);
107   setWidget(scroll_widget);
108   setWindowTitle("Job Manager");
109   setObjectName("jmJobManagerDock");
110 }
111
112 BL::JobsManager_QT::~JobsManager_QT()
113 {
114   DEBTRACE("Destroying BL::JobsManager_QT");
115 }
116
117 void
118 BL::JobsManager_QT::set_model_manager(BL::QModelManager * model_manager)
119 {
120   _model_manager = model_manager;
121 }
122
123 void
124 BL::JobsManager_QT::load_jobs_button()
125 {
126   DEBTRACE("load_jobs");
127   QString jobs_file = QFileDialog::getOpenFileName(this,
128                                                    tr("Choose an xml jobs file"), "",
129                                                    tr("xml (*.xml);;All Files (*)"));
130   if (jobs_file == "")
131   {
132     write_normal_text("Load jobs action cancelled\n");
133   }
134   else
135     load_jobs(jobs_file.toUtf8().constData());
136 }
137
138 void
139 BL::JobsManager_QT::save_jobs_button()
140 {
141   DEBTRACE("save_jobs");
142   QFileDialog dialog(this, "Save jobs file");
143   QStringList filters;
144   filters << "XML files (*.xml)"
145           << "Any files (*)";
146   dialog.setFileMode(QFileDialog::AnyFile);
147   dialog.setNameFilters(filters);
148   dialog.selectNameFilter("(*.xml)");
149   dialog.setDefaultSuffix("xml");
150   dialog.setConfirmOverwrite(true);
151   dialog.setAcceptMode(QFileDialog::AcceptSave);
152   QString jobs_file("");
153   QStringList fileNames;
154   fileNames.clear();
155   if (bool ret = dialog.exec())
156   {
157     DEBTRACE(ret << " " << dialog.confirmOverwrite());
158     fileNames = dialog.selectedFiles();
159     if (!fileNames.isEmpty())
160       jobs_file= fileNames.first();
161   }
162   if (jobs_file == "")
163   {
164     write_normal_text("Save jobs action cancelled\n");
165   }
166   else
167     save_jobs(jobs_file.toUtf8().constData());
168 }
169
170 void
171 BL::JobsManager_QT::RefreshJobs()
172 {
173   refresh_jobs(); 
174 }
175
176 void
177 BL::JobsManager_QT::no_auto_refresh()
178 {
179   _auto_refresh_jobs->setText("Auto Refresh: no");
180   _timer->stop();
181 }
182
183 void
184 BL::JobsManager_QT::ten_seconds_refresh()
185 {
186   _auto_refresh_jobs->setText("Auto Refresh: 10s");
187   _timer->stop();
188   _timer->start(10 * 1000);
189 }
190
191 void 
192 BL::JobsManager_QT::thirty_seconds_refresh()
193 {
194   _auto_refresh_jobs->setText("Auto Refresh: 30s");
195   _timer->stop();
196   _timer->start(30 * 1000);
197 }
198
199 void 
200 BL::JobsManager_QT::one_minute_refresh()
201 {
202   _auto_refresh_jobs->setText("Auto Refresh: 1min");
203   _timer->stop();
204   _timer->start(1 * 60 * 1000);
205 }
206
207 void 
208 BL::JobsManager_QT::five_minutes_refresh()
209 {
210   _auto_refresh_jobs->setText("Auto Refresh: 5min");
211   _timer->stop();
212   _timer->start(5 * 60 * 1000);
213 }
214
215 void 
216 BL::JobsManager_QT::thirty_minutes_refresh()
217 {
218   _auto_refresh_jobs->setText("Auto Refresh: 30min");
219   _timer->stop();
220   _timer->start(30 * 60 * 1000);
221 }
222
223 void 
224 BL::JobsManager_QT::one_hour_refresh()
225 {
226   _auto_refresh_jobs->setText("Auto Refresh: 1hour");
227   _timer->stop();
228   _timer->start(1 * 60 * 60 * 1000);
229 }
230
231 void 
232 BL::JobsManager_QT::restart_job(const std::string & name)
233 {
234   DEBTRACE("Restart job with name: " << name);
235   BL::CreateJobWizard wizard(this, _salome_services);
236   wizard.clone(name);
237   wizard.end(1);
238   wizard.job_name = name;
239   wizard.start_job = true;
240   _main_gui->delete_job_internal();
241   create_job_with_wizard(wizard);
242 }
243
244 void 
245 BL::JobsManager_QT::edit_clone_job(const std::string & name)
246 {
247   BL::CreateJobWizard wizard(this, _salome_services);
248   wizard.clone(name);
249   wizard.exec();
250
251   // Check if the job has the same name
252   if (name == wizard.job_name)
253   {
254     DEBTRACE("Job " << name << " has been edited");
255     _main_gui->delete_job_internal();
256   }
257
258   if (wizard.job_name != "")
259   {
260     create_job_with_wizard(wizard);
261   }
262   else
263   {
264     DEBTRACE("User cancel Create Job Wizard");
265   }
266 }
267
268 void
269 BL::JobsManager_QT::create_job()
270 {
271     BL::CreateJobWizard wizard(this, _salome_services);
272     wizard.exec();
273     if (wizard.job_name != "")
274     {
275       create_job_with_wizard(wizard);
276     }
277     else
278     {
279        DEBTRACE("User cancel Create Job Wizard");
280     }
281 }
282
283 void 
284 BL::JobsManager_QT::create_job_with_wizard(BL::CreateJobWizard & wizard)
285 {
286   BL::Job * new_job = createJob(wizard.job_name);
287   switch (wizard.job_type)
288   {
289   case BL::CreateJobWizard::YACS:
290     // YACS schema job
291     new_job->setType(BL::Job::YACS_SCHEMA);
292     new_job->setJobFile(wizard.yacs_file);
293     new_job->setDumpYACSState(wizard.dump_yacs_state);
294     new_job->setYacsDriverOptions(wizard.yacs_driver_options);
295     break;
296   case BL::CreateJobWizard::COMMAND:
297     // Command Job
298     new_job->setType(BL::Job::COMMAND);
299     new_job->setJobFile(wizard.command);
300     break;
301   case BL::CreateJobWizard::PYTHON_SALOME:
302     // Python Salome Job
303     new_job->setType(BL::Job::PYTHON_SALOME);
304     new_job->setJobFile(wizard.python_salome_file);
305     break;
306   default:
307     throw BL::Exception("Unknown job type");
308   }
309
310   // For all jobs
311   new_job->setEnvFile(wizard.env_file);
312   BL::Job::BatchParam param;
313
314   // For COORM
315   if (wizard.coorm_batch_directory != "")
316   {
317         param.batch_directory = wizard.coorm_batch_directory;
318   }
319   else if (wizard.batch_directory != "")
320   {
321         param.batch_directory = wizard.batch_directory;
322   }
323
324   param.maximum_duration = wizard.maximum_duration;
325   param.mem_limit = wizard.mem_limit;
326   param.mem_req_type = wizard.mem_req_type;
327   param.nb_proc = wizard.nb_proc;
328   param.exclusive = wizard.exclusive;
329
330   // Parameters for COORM
331   param.launcher_file = wizard.launcher_file;
332   param.launcher_args = wizard.launcher_args;
333
334   new_job->setBatchParameters(param);
335   BL::Job::FilesParam files_params;
336   files_params.result_directory = wizard.result_directory;
337   files_params.input_files_list = wizard.input_files_list;
338   files_params.output_files_list = wizard.output_files_list;
339   new_job->setFilesParameters(files_params);
340   new_job->setResource(wizard.resource_choosed);
341   new_job->setBatchQueue(wizard.batch_queue);
342   new_job->setLoadLevelerJobType(wizard.ll_jobtype);
343   new_job->setWCKey(wizard.wckey);
344   new_job->setExtraParams(wizard.extra_params);
345
346   // End
347   addJobToLauncher(wizard.job_name);
348   emit new_job_added(QString::fromUtf8(wizard.job_name.c_str()));
349   QStandardItemModel * model = _model_manager->getModel();
350   QList<QStandardItem *> item_list = model->findItems(QString::fromUtf8(wizard.job_name.c_str()));
351   QStandardItem * job_state_item = model->item(item_list.at(0)->row(), 2);
352   _main_gui->_jobs_table->selectRow(item_list.at(0)->row());
353   if (wizard.start_job)
354     start_job(wizard.job_name);
355 }
356
357 void
358 BL::JobsManager_QT::delete_job(QString job_name)
359 {
360   BL::JobsManager::removeJob(job_name.toUtf8().constData());
361   _model_manager->delete_job(job_name);
362   _main_gui->_job_tab->reset(job_name);
363 }
364
365 void 
366 BL::JobsManager_QT::sendEvent(const std::string & action, 
367                               const std::string & event_name, 
368                               const std::string & job_name, 
369                               const std::string & data)
370 {
371   DEBTRACE("sendEvent BL::JobsManager_QT");
372
373   // Sending a QEvent to go back to main thread
374   BL::JobManagerEvent * event = new JobManagerEvent(action, event_name, job_name, data);
375   QApplication::postEvent(this, event);
376 }
377
378 bool 
379 BL::JobsManager_QT::event(QEvent * e)
380 {
381   QDockWidget::event(e);
382   JobManagerEvent * event = dynamic_cast<JobManagerEvent*>(e);
383   if (!event) return false;
384
385   DEBTRACE("BL::JobsManager_QT Receiving event : " 
386            << event->action << " " 
387            << event->event_name << " "
388            << event->job_name << " "
389            << event->data);
390
391   QString job_name = QString::fromUtf8(event->job_name.c_str());
392   if (event->action == "create_job")
393   {
394     if (event->event_name == "Ok")
395     {
396       write_normal_text("Job " + job_name + " created\n");
397     }
398     else
399     {
400       write_error_text("Error in creating job: " + job_name + "\n");
401       write_error_text("*** ");
402       write_error_text((event->data).c_str());
403       write_error_text(" ***\n");
404     }
405   }
406   else if (event->action == "start_job")
407   {
408     if (event->event_name == "Ok")
409     {
410       write_normal_text("Job " + job_name + " queued\n");
411     }
412     else
413     {
414       write_error_text("Error in starting job: " + job_name + "\n");
415       write_error_text("*** ");
416       write_error_text((event->data).c_str());
417       write_error_text(" ***\n");
418     }
419     emit job_state_changed(job_name);
420   }
421   else if (event->action == "refresh_job")
422   {
423     if (event->event_name == "Ok")
424     {
425       QString state((event->data).c_str());
426       state = state.toLower();
427       write_normal_text("Job " + job_name + " new state is " + state + "\n");
428       emit job_state_changed(job_name);
429     }
430     else
431     {
432       write_error_text("Error in refreshing job: " + job_name + "\n");
433       write_error_text("*** ");
434       write_error_text((event->data).c_str());
435       write_error_text(" ***\n");
436     }
437   }
438   else if (event->action == "delete_job")
439   {
440     if (event->event_name == "Ok")
441     {
442       write_normal_text("Job " + job_name + " deleted\n");
443     }
444     else
445     {
446       write_error_text("Warning delete job: " + job_name + " maybe not complete, exception catch in SALOME Launcher service\n");
447       write_error_text("*** ");
448       write_error_text((event->data).c_str());
449       write_error_text(" ***\n");
450     }
451   }
452   else if (event->action == "get_results_job")
453   {
454     if (event->event_name == "Ok")
455     {
456       write_normal_text("Results of Job " + job_name + " are get\n");
457     }
458     else
459     {
460       write_error_text("Warning for results of job: " + job_name + " maybe not complete, exception catch in SALOME Launcher service\n");
461       write_error_text("*** ");
462       write_error_text((event->data).c_str());
463       write_error_text(" ***\n");
464     }
465   }
466   else if (event->action == "stop_job")
467   {
468     if (event->event_name == "Ok")
469     {
470       write_normal_text("Job " + job_name + " is stopped\n");
471     }
472     else
473     {
474       write_error_text("Error when trying to stop job: " + job_name + "\n");
475       write_error_text("*** ");
476       write_error_text((event->data).c_str());
477       write_error_text(" ***\n");
478     }
479   }
480   else if (event->action == "get_assigned_hostnames")
481   {
482     if (event->event_name == "Ok")
483     {
484           vector<string> hostnames;
485
486           Tokenize(event->data, hostnames, "+");
487
488           vector<string>::iterator it;
489
490       write_normal_text("Job " + job_name + " assigned hostnames are :\n");
491
492           for (it = hostnames.begin(); it < hostnames.end(); it++)
493           {
494                   vector<string> hostname;
495                   Tokenize(*it, hostname, ".");
496                   QString assigned_hostname(hostname[0].c_str());
497                   write_normal_text("+ " + assigned_hostname + "\n");
498           }
499     }
500     else
501     {
502                 // Do nothing in case the batch manager does not support this
503     }
504   }
505   else if (event->action == "save_jobs")
506   {
507     if (event->event_name == "Error")
508     {
509       write_error_text("Error in saving jobs: \n");
510       write_error_text("*** ");
511       write_error_text((event->data).c_str());
512       write_error_text(" ***\n");
513     }
514     else
515     {
516       QString str((event->data).c_str());
517       write_normal_text("Jobs saved in file " + str + "\n");
518     }
519   }
520   else if (event->action == "load_jobs")
521   {
522     if (event->event_name == "Error")
523     {
524       write_error_text("Error in loading jobs: \n");
525       write_error_text("*** ");
526       write_error_text((event->data).c_str());
527       write_error_text(" ***\n");
528     }
529     else
530     {
531       QString str((event->data).c_str());
532       write_normal_text("Jobs loaded from file " + str + "\n");
533     }
534   }
535   else if (event->action == "add_job")
536   {
537     if (event->event_name == "Ok")
538     {
539       write_normal_text("New job added " + job_name + "\n");
540       emit new_job_added(job_name);
541     }
542   }
543   else if (event->action == "to_remove_job")
544   {
545     if (event->event_name == "Ok")
546       _main_gui->delete_job_external(job_name);
547   }
548   else
549   {
550     QString str((event->action).c_str());
551     write_error_text("Unknown type of event received:" + str + "\n");
552   }
553   return true;
554 }
555
556 void 
557 BL::JobsManager_QT::write_normal_text(const QString & text)
558 {
559   _log->setReadOnly(false);
560   QTextCursor cursor = _log->textCursor();
561   QTextCharFormat text_format;
562   text_format.setForeground(Qt::darkBlue);
563   cursor.insertText(text, text_format);
564   _log->setTextCursor(cursor);
565   _log->setReadOnly(true);
566 }
567
568 void 
569 BL::JobsManager_QT::write_error_text(const QString & text)
570 {
571   _log->setReadOnly(false);
572   QTextCursor cursor = _log->textCursor();
573   QTextCharFormat text_format;
574   text_format.setForeground(Qt::red);
575   cursor.insertText(text, text_format);
576   _log->setTextCursor(cursor);
577   _log->setReadOnly(true);
578 }
579
580 // To tokenize a string
581 void Tokenize(const std::string& str, std::vector<std::string>& tokens, const std::string& delimiters)
582 {
583         // Skip delimiters at beginning.
584         string::size_type lastPos = str.find_first_not_of(delimiters, 0);
585         // Find first "non-delimiter".
586         string::size_type pos     = str.find_first_of(delimiters, lastPos);
587
588         while (string::npos != pos || string::npos != lastPos)
589         {
590                 // Found a token, add it to the vector.
591                 tokens.push_back(str.substr(lastPos, pos - lastPos));
592                 // Skip delimiters.  Note the "not_of"
593                 lastPos = str.find_first_not_of(delimiters, pos);
594                 // Find next "non-delimiter"
595                 pos = str.find_first_of(delimiters, lastPos);
596         }
597 }