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