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