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