]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/job/job.cpp
67ad1b4cd2c82e1b8301d6c4228e1eebba5ca626
[bacula/bacula] / bacula / src / qt-console / job / job.cpp
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2016 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19  
20 #include "bat.h"
21 #include "job.h"
22 #include "util/fmtwidgetitem.h"
23 #include "mediainfo/mediainfo.h"
24 #include "run/run.h"
25
26 Job::Job(QString &jobId, QTreeWidgetItem *parentTreeWidgetItem) : Pages()
27 {
28    setupUi(this);
29    pgInitialize(tr("Job"), parentTreeWidgetItem);
30    QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
31    thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/joblog.png")));
32    m_cursor = new QTextCursor(textJobLog->document());
33
34    m_bwlimit = 0;
35    m_jobId = jobId;
36    m_timer = NULL;
37    getFont();
38
39    connect(pbRefresh, SIGNAL(clicked()), this, SLOT(populateAll()));
40    connect(pbDelete, SIGNAL(clicked()), this, SLOT(deleteJob()));
41    connect(pbCancel, SIGNAL(clicked()), this, SLOT(cancelJob()));
42    connect(pbRun, SIGNAL(clicked()), this, SLOT(rerun()));
43    connect(list_Volume, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(showInfoVolume(QListWidgetItem *)));
44    connect(spin_Bwlimit, SIGNAL(valueChanged(int)), this, SLOT(storeBwLimit(int)));
45
46    populateAll();
47    dockPage();
48    setCurrent();
49 }
50
51 void Job::rerun()
52 {
53    new runPage(label_Name->text(),
54                label_Level->text(),
55                label_Pool->text(),
56                QString(""),              // storage
57                label_Client->text(),
58                label_FileSet->text());
59 }
60
61 void Job::showInfoVolume(QListWidgetItem *item)
62 {
63    QString s= item->text();
64    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
65
66    MediaInfo *m = new MediaInfo(pageSelectorTreeWidgetItem, s);
67    connect(m, SIGNAL(destroyed()), this, SLOT(populateTree()));
68 }
69
70 void Job::deleteJob()
71 {
72    if (QMessageBox::warning(this, "Bat",
73       tr("Are you sure you want to delete??  !!!.\n"
74 "This delete command is used to delete a Job record and all associated catalog"
75 " records that were created. This command operates only on the Catalog"
76 " database and has no effect on the actual data written to a Volume. This"
77 " command can be dangerous and we strongly recommend that you do not use"
78 " it unless you know what you are doing.  The Job and all its associated"
79 " records (File and JobMedia) will be deleted from the catalog."
80       "Press OK to proceed with delete operation.?"),
81       QMessageBox::Ok | QMessageBox::Cancel)
82       == QMessageBox::Cancel) { return; }
83
84    QString cmd("delete job jobid=");
85    cmd += m_jobId;
86    consoleCommand(cmd, false);
87    closeStackPage();
88 }
89
90 void Job::cancelJob()
91 {
92    if (QMessageBox::warning(this, "Bat",
93                             tr("Are you sure you want to cancel this job?"),
94                             QMessageBox::Ok | QMessageBox::Cancel)
95        == QMessageBox::Cancel) { return; }
96
97    QString cmd("cancel jobid=");
98    cmd += m_jobId;
99    consoleCommand(cmd, false);
100 }
101
102 void Job::getFont()
103 {
104    QFont font = textJobLog->font();
105
106    QString dirname;
107    m_console->getDirResName(dirname);
108    QSettings settings(dirname, "bat");
109    settings.beginGroup("Console");
110    font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
111    font.setPointSize(settings.value("consolePointSize", 10).toInt());
112    font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
113    settings.endGroup();
114    textJobLog->setFont(font);
115 }
116
117 void Job::populateAll()
118 {
119 // Pmsg0(50, "populateAll()\n");
120    populateText();
121    populateForm();
122    populateVolumes();
123 }
124
125 /*
126  * Populate the text in the window
127  * TODO: Just append new text instead of clearing the window
128  */
129 void Job::populateText()
130 {
131    textJobLog->clear();
132    QString query;
133    query = "SELECT Time, LogText FROM Log WHERE JobId='" + m_jobId + "' order by Time";
134
135    /* This could be a log item */
136    if (mainWin->m_sqlDebug) {
137       Pmsg1(000, "Log query cmd : %s\n", query.toUtf8().data());
138    }
139   
140    QStringList results;
141    if (m_console->sql_cmd(query, results)) {
142
143       if (!results.size()) {
144          QMessageBox::warning(this, tr("Bat"),
145             tr("There were no results!\n"
146                "It is possible you may need to add \"catalog = all\" "
147                "to the Messages resource for this job.\n"), QMessageBox::Ok);
148          return;
149       } 
150
151       QString jobstr("JobId "); /* FIXME: should this be translated ? */
152       jobstr += m_jobId;
153
154       QString htmlbuf("<html><body><pre>");
155   
156       /* Iterate through the lines of results. */
157       QString field;
158       QStringList fieldlist;
159       QString lastTime;
160       QString lastSvc;
161       foreach (QString resultline, results) {
162          fieldlist = resultline.split("\t");
163          
164          if (fieldlist.size() < 2)
165             continue;
166
167          QString curTime = fieldlist[0].trimmed();
168
169          field = fieldlist[1].trimmed();
170          int colon = field.indexOf(":");
171          if (colon > 0) {
172             /* string is like <service> <jobId xxxx>: ..." 
173              * we split at ':' then remove the jobId xxxx string (always the same) */ 
174             QString curSvc(field.left(colon).replace(jobstr,"").trimmed());
175             if (curSvc == lastSvc  && curTime == lastTime) {
176                curTime.clear();
177                curSvc.clear(); 
178             } else {
179                lastTime = curTime;
180                lastSvc = curSvc;
181             }
182 //          htmlbuf += "<td>" + curTime + "</td>";
183             htmlbuf += "\n" + curSvc + " ";
184
185             /* rest of string is marked as pre-formatted (here trimming should
186              * be avoided, to preserve original formatting) */
187             QString msg(field.mid(colon+2));
188             if (msg.startsWith( tr("Error:")) ) { /* FIXME: should really be translated ? */
189                /* error msg, use a specific class */
190                htmlbuf += "</pre><pre class=err>" + msg + "</pre><pre>";
191             } else {
192                htmlbuf += msg ;
193             }
194          } else {
195             /* non standard string, place as-is */
196             if (curTime == lastTime) {
197                curTime.clear();
198             } else {
199                lastTime = curTime;
200             }
201 //          htmlbuf += "<td>" + curTime + "</td>";
202             htmlbuf += "\n" + field ;
203          }
204   
205       } /* foreach resultline */
206
207       htmlbuf += "</pre></body></html>";
208
209       /* full text ready. Here a custom sheet is used to align columns */
210       QString logSheet(".err {color:#FF0000;}");
211       textJobLog->document()->setDefaultStyleSheet(logSheet);
212       textJobLog->document()->setHtml(htmlbuf); 
213       textJobLog->moveCursor(QTextCursor::Start);
214
215    } /* if results from query */
216   
217 }
218
219 void Job::storeBwLimit(int val)
220 {
221    m_bwlimit = val;
222 }
223
224 void Job::updateRunInfo()
225 {
226    QString cmd;
227    QStringList results;
228    QStringList lst;
229    bool parseit=false;
230
231 #ifdef xxx
232    /* This doesn't seem like the right thing to do */
233    if (m_bwlimit >= 100) {
234       cmd = QString("setbandwidth limit=" + QString::number(m_bwlimit) 
235                     + " jobid=" + m_jobId);
236       m_console->dir_cmd(cmd, results);
237       results.clear();
238       m_bwlimit = 0;
239    }
240 #endif
241
242    cmd = QString(".status client=\"" + m_client + "\" running");
243 /*
244  *  JobId 5 Job backup.2010-12-21_09.28.17_03 is running.
245  *   VSS Full Backup Job started: 21-Dec-10 09:28
246  *   Files=4 Bytes=610,976 Bytes/sec=87,282 Errors=0
247  *   Files Examined=4
248  *   Processing file: /tmp/regress/build/po/de.po
249  *   SDReadSeqNo=5 fd=5
250  *
251  *  Or
252  *  JobId=5
253  *  Job=backup.2010-12-21_09.28.17_03
254  *  VSS=1
255  *  Files=4
256  *  Bytes=610976
257  *
258  */
259    QRegExp jobline("(JobId) (\\d+) Job ");
260    QRegExp itemline("([\\w /]+)[:=]\\s*(.+)");
261    QRegExp filesline("Files: Examined=([\\d,]+) Backed up=([\\d,])");
262    QRegExp oldline("Files=([\\d,]+) Bytes=([\\d,]+) Bytes/sec=([\\d,]+) Errors=([\\d,]+)");
263    QRegExp restoreline("Files: Restored=([\\d,]+) Expected=([\\d,]+) Completed=([\\d,]+)%");
264    QRegExp restoreline2("Files Examined=([\\d,]+) Expected Files=([\\d,]+) Percent Complete=([\\d,]+)");
265
266    QString com(",");
267    QString empty("");
268    
269    if (m_console->dir_cmd(cmd, results)) {
270       foreach (QString mline, results) {
271          foreach (QString line, mline.split("\n")) { 
272             line = line.trimmed();
273             if (oldline.indexIn(line) >= 0) {
274                if (parseit) {
275                   lst = oldline.capturedTexts();
276                   label_JobErrors->setText(lst[4]);
277                   label_Speed->setText(convertBytesSI(lst[3].replace(com, empty).toULongLong())+"/s");
278                   label_JobFiles->setText(lst[1]);
279                   label_JobBytes->setText(convertBytesSI(lst[2].replace(com, empty).toULongLong()));
280                }
281                continue;
282
283             } else if (filesline.indexIn(line) >= 0) {
284                if (parseit) {
285                   lst = filesline.capturedTexts(); // Will also catch Backed up
286                   label_FilesExamined->setText(lst[1]);
287                }
288                continue;
289
290 // TODO: Need to be fixed
291 //            } else if (restoreline2.indexIn(line) >= 0) {
292 //               if (parseit) {
293 //                  lst = filesline.capturedTexts();
294 //                  label_FilesExamined->setText(lst[1]); // Can also handle Expected and Completed
295 //               }
296 //               continue;
297
298             } else if (jobline.indexIn(line) >= 0) {
299                lst = jobline.capturedTexts();
300                lst.removeFirst();
301
302             } else if (itemline.indexIn(line) >= 0) {
303                lst = itemline.capturedTexts();
304                lst.removeFirst();
305
306             } else {
307                if (mainWin->m_miscDebug) 
308                   Pmsg1(0, "bad line=%s\n", line.toUtf8().data());
309                continue;
310             }
311             if (lst.count() < 2) {
312                if (mainWin->m_miscDebug) 
313                   Pmsg2(0, "bad line=%s count=%d\n", line.toUtf8().data(), lst.count());
314             }
315             if (lst[0] == "JobId") {
316                if (lst[1] == m_jobId) {
317                   parseit = true;
318                } else {
319                   parseit = false;
320                }
321             }
322             if (!parseit) {
323                continue;
324             }
325             
326 //         } else if (lst[0] == "Job") {
327 //            grpRun->setTitle(lst[1]);
328             
329 //               
330 //         } else if (lst[0] == "VSS") {
331
332 //         } else if (lst[0] == "Level") {
333 //            Info->setText(lst[1]);
334 //
335 //         } else if (lst[0] == "JobType" || lst[0] == "Type") {
336 //
337 //         } else if (lst[0] == "JobStarted" || lst[0] == "StartTime") {
338 //            Started->setText(lst[1]);
339
340 #ifdef xxx
341             if (lst[0] == "Bwlimit") {
342                int val = lst[1].toInt();
343                if (val > 0) {
344                   chk_Bwlimit->setChecked(true);
345                   spin_Bwlimit->setEnabled(true);
346                   spin_Bwlimit->setValue(lst[1].toInt()/1024);
347                } else {
348                   chk_Bwlimit->setEnabled(false);
349                   spin_Bwlimit->setEnabled(false);
350                   spin_Bwlimit->setValue(0);
351                }
352 #endif
353                
354             if (lst[0] == "Errors") {
355                label_JobErrors->setText(lst[1]);
356                
357             } else if (lst[0] == "Bytes/sec") {
358                label_Speed->setText(convertBytesSI(lst[1].toULongLong())+"/s");
359                
360             } else if (lst[0] == "Files" || lst[0] == "JobFiles") {
361                label_JobFiles->setText(lst[1]);
362                
363             } else if (lst[0] == "Bytes" || lst[0] == "JobBytes") {
364                label_JobBytes->setText(convertBytesSI(lst[1].toULongLong()));
365                
366             } else if (lst[0] == "Examined") {
367                label_FilesExamined->setText(lst[1]);
368
369             } else if (lst[0] == "Files Examined") {
370                label_FilesExamined->setText(lst[1]);
371                
372             } else if (lst[0] == "Processing file") {
373                label_CurrentFile->setText(lst[1]);
374             }
375          }
376       }
377    }
378 }
379
380 /*
381  * Populate the text in the window
382  */
383 void Job::populateForm()
384 {
385    QString stat, err;
386    char buf[256];
387    QString query = 
388       "SELECT JobId, Job.Name, Level, Client.Name, Pool.Name, FileSet,"
389       "SchedTime, StartTime, EndTime, EndTime-StartTime AS Duration, "
390       "JobBytes, JobFiles, JobErrors, JobStatus, PurgedFiles "
391       "FROM Job JOIN Client USING (ClientId) "
392         "LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId) "
393         "LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)"
394       "WHERE JobId=" + m_jobId; 
395    QStringList results;
396    if (m_console->sql_cmd(query, results)) {
397       QString resultline, duration;
398       QStringList fieldlist;
399
400       foreach (resultline, results) { // should have only one result
401          fieldlist = resultline.split("\t");
402          if (fieldlist.size() != 15) {
403             Pmsg1(000, "Unexpected line %s", resultline.toUtf8().data());
404             continue;
405          }
406          QStringListIterator fld(fieldlist);
407          label_JobId->setText(fld.next());
408          label_Name->setText(fld.next());
409          
410          label_Level->setText(job_level_to_str(fld.next()[0].toAscii()));
411
412          m_client = fld.next();
413          label_Client->setText(m_client);
414          label_Pool->setText(fld.next());
415          label_FileSet->setText(fld.next());
416          label_SchedTime->setText(fld.next());
417          label_StartTime->setText(fld.next());
418          label_EndTime->setText(fld.next());
419          duration = fld.next();
420          /* 
421           * Note: if we have a negative duration, it is because the EndTime
422           *  is zero (i.e. the Job is still running).  We should use 
423           *  duration = StartTime - current_time
424           */
425          if (duration.left(1) == "-") {
426             duration = "0.0";
427          }
428          label_Duration->setText(duration);
429
430          label_JobBytes->setText(convertBytesSI(fld.next().toULongLong()));
431          label_JobFiles->setText(fld.next());
432          err = fld.next();
433          label_JobErrors->setText(err);
434
435          stat = fld.next();
436          if (stat == "T" && err.toInt() > 0) {
437             stat = "W";
438          }
439          if (stat == "R") {
440             pbDelete->setVisible(false);
441             pbCancel->setVisible(true);
442             grpRun->setVisible(true);
443             if (!m_timer) {
444                m_timer = new QTimer(this);
445                connect(m_timer, SIGNAL(timeout()), this, SLOT(populateAll()));
446                m_timer->start(30000);
447             }
448             updateRunInfo();
449          } else {
450             pbDelete->setVisible(true);
451             pbCancel->setVisible(false);
452             grpRun->setVisible(false);
453             if (m_timer) {
454                m_timer->stop();
455                delete m_timer;
456                m_timer = NULL;
457             }
458          }
459          label_JobStatus->setPixmap(QPixmap(":/images/" + stat + ".png"));
460          jobstatus_to_ascii_gui(stat[0].toAscii(), buf, sizeof(buf));
461          stat = buf;
462          label_JobStatus->setToolTip(stat);
463
464          chkbox_PurgedFiles->setCheckState(fld.next().toInt()?Qt::Checked:Qt::Unchecked);
465       }
466    }
467 }
468   
469 void Job::populateVolumes()
470 {
471
472    QString query = 
473       "SELECT DISTINCT VolumeName, InChanger, Slot "
474       "FROM Job JOIN JobMedia USING (JobId) JOIN Media USING (MediaId) "
475       "WHERE JobId=" + m_jobId + " ORDER BY VolumeName "; 
476    if (mainWin->m_sqlDebug) Pmsg1(0, "Query cmd : %s\n",query.toUtf8().data());
477          
478
479    QStringList results;
480    if (m_console->sql_cmd(query, results)) {
481       QString resultline;
482       QStringList fieldlist;
483       list_Volume->clear();
484       foreach (resultline, results) { // should have only one result
485          fieldlist = resultline.split("\t");
486          QStringListIterator fld(fieldlist);
487 //         QListWidgetItem(QIcon(":/images/inchanger" + fld.next() + ".png"), 
488 //                         fld.next(), list_Volume);
489          list_Volume->addItem(fld.next());
490       }
491    }
492 }
493
494 //QListWidgetItem ( const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type )