]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/job/job.cpp
Modify Job view to follow backup progress in real-time
[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)
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  */
137 void Job::populateText()
138 {
139    textJobLog->clear();
140    QString query;
141    query = "SELECT Time, LogText FROM Log WHERE JobId='" + m_jobId + "' order by Time";
142
143    /* This could be a log item */
144    if (mainWin->m_sqlDebug) {
145       Pmsg1(000, "Log query cmd : %s\n", query.toUtf8().data());
146    }
147   
148    QStringList results;
149    if (m_console->sql_cmd(query, results)) {
150
151       if (!results.size()) {
152          QMessageBox::warning(this, tr("Bat"),
153             tr("There were no results!\n"
154                "It is possible you may need to add \"catalog = all\" "
155                "to the Messages resource for this job.\n"), QMessageBox::Ok);
156          return;
157       } 
158
159       QString jobstr("JobId "); /* FIXME: should this be translated ? */
160       jobstr += m_jobId;
161
162       QString htmlbuf("<html><body><pre>");
163   
164       /* Iterate through the lines of results. */
165       QString field;
166       QStringList fieldlist;
167       QString lastTime;
168       QString lastSvc;
169       foreach (QString resultline, results) {
170          fieldlist = resultline.split("\t");
171          
172          if (fieldlist.size() < 2)
173             continue;
174
175          QString curTime = fieldlist[0].trimmed();
176
177          field = fieldlist[1].trimmed();
178          int colon = field.indexOf(":");
179          if (colon > 0) {
180             /* string is like <service> <jobId xxxx>: ..." 
181              * we split at ':' then remove the jobId xxxx string (always the same) */ 
182             QString curSvc(field.left(colon).replace(jobstr,"").trimmed());
183             if (curSvc == lastSvc  && curTime == lastTime) {
184                curTime.clear();
185                curSvc.clear(); 
186             } else {
187                lastTime = curTime;
188                lastSvc = curSvc;
189             }
190 //          htmlbuf += "<td>" + curTime + "</td>";
191             htmlbuf += "\n" + curSvc + " ";
192
193             /* rest of string is marked as pre-formatted (here trimming should
194              * be avoided, to preserve original formatting) */
195             QString msg(field.mid(colon+2));
196             if (msg.startsWith( tr("Error:")) ) { /* FIXME: should really be translated ? */
197                /* error msg, use a specific class */
198                htmlbuf += "</pre><pre class=err>" + msg + "</pre><pre>";
199             } else {
200                htmlbuf += msg ;
201             }
202          } else {
203             /* non standard string, place as-is */
204             if (curTime == lastTime) {
205                curTime.clear();
206             } else {
207                lastTime = curTime;
208             }
209 //          htmlbuf += "<td>" + curTime + "</td>";
210             htmlbuf += "\n" + field ;
211          }
212   
213       } /* foreach resultline */
214
215       htmlbuf += "</pre></body></html>";
216
217       /* full text ready. Here a custom sheet is used to align columns */
218       QString logSheet(".err {color:#FF0000;}");
219       textJobLog->document()->setDefaultStyleSheet(logSheet);
220       textJobLog->document()->setHtml(htmlbuf); 
221       textJobLog->moveCursor(QTextCursor::Start);
222
223    } /* if results from query */
224   
225 }
226
227 void Job::storeBwLimit(int val)
228 {
229    m_bwlimit = val;
230 }
231
232 void Job::updateRunInfo()
233 {
234    QString cmd;
235    QStringList results;
236    QStringList lst;
237    bool parseit=false;
238    QChar equal = '=';
239
240    if (m_bwlimit >= 100) {
241       cmd = QString("setbandwidth limit=" + QString::number(m_bwlimit) 
242                     + " jobid=" + m_jobId);
243       m_console->dir_cmd(cmd, results);
244       results.clear();
245       m_bwlimit = 0;
246    }
247
248    cmd = QString(".status client=\"" + m_client + "\" running");
249
250    if (m_console->dir_cmd(cmd, results)) {
251       foreach (QString mline, results) {
252          foreach (QString line, mline.split("\n")) { 
253             line = line.trimmed();
254             lst = line.split(equal);
255             if (lst.count() != 2) {
256                Pmsg1(0, "bad count=%d\n",lst.count());
257                continue;
258             }
259             
260             if (lst[0] == "JobId") {
261                if (lst[1] == m_jobId) {
262                   parseit = true;
263                } else {
264                   parseit = false;
265                }
266             }
267             if (!parseit) {
268                continue;
269             }
270             
271 //         } else if (lst[0] == "Job") {
272 //            grpRun->setTitle(lst[1]);
273             
274 //               
275 //         } else if (lst[0] == "VSS") {
276
277 //         } else if (lst[0] == "Level") {
278 //            Info->setText(lst[1]);
279 //
280 //         } else if (lst[0] == "JobType") {
281 //
282 //         } else if (lst[0] == "JobStarted") {
283 //            Started->setText(lst[1]);
284
285             if (lst[0] == "Bwlimit") {
286                int val = lst[1].toInt();
287                if (val > 0) {
288                   chk_Bwlimit->setChecked(true);
289                   spin_Bwlimit->setEnabled(true);
290                   spin_Bwlimit->setValue(lst[1].toInt()/1024);
291                } else {
292                   chk_Bwlimit->setEnabled(false);
293                   spin_Bwlimit->setEnabled(false);
294                   spin_Bwlimit->setValue(0);
295                }
296                
297 //         } else if (lst[0] == "Errors") {
298 //            Errors->setText(lst[1]);
299                
300             } else if (lst[0] == "Bytes/sec") {
301                label_Speed->setText(convertBytesSI(lst[1].toULongLong())+"/s");
302                
303             } else if (lst[0] == "Files") {
304                label_JobFiles->setText(lst[1]);
305                
306             } else if (lst[0] == "Bytes") {
307                label_JobBytes->setText(convertBytesSI(lst[1].toULongLong()));
308                
309             } else if (lst[0] == "FilesExamined") {
310                label_FilesExamined->setText(lst[1]);
311                
312             } else if (lst[0] == "ProcessingFile") {
313                label_CurrentFile->setText(lst[1]);
314                
315             }
316          }
317       }
318    }
319 }
320
321 /*
322  * Populate the text in the window
323  */
324 void Job::populateForm()
325 {
326    QString stat, err;
327    char buf[256];
328    QString query = 
329       "SELECT JobId, Job.Name, Level, Client.Name, Pool.Name, FileSet,"
330       "SchedTime, StartTime, EndTime, EndTime-StartTime AS Duration, "
331       "JobBytes, JobFiles, JobErrors, JobStatus, PurgedFiles "
332       "FROM Job JOIN Client USING (ClientId) "
333         "LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId) "
334         "LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)"
335       "WHERE JobId=" + m_jobId; 
336    QStringList results;
337    if (m_console->sql_cmd(query, results)) {
338       QString resultline, duration;
339       QStringList fieldlist;
340
341       foreach (resultline, results) { // should have only one result
342          fieldlist = resultline.split("\t");
343          QStringListIterator fld(fieldlist);
344          label_JobId->setText(fld.next());
345          label_Name->setText(fld.next());
346          
347          label_Level->setText(job_level_to_str(fld.next()[0].toAscii()));
348
349          m_client = fld.next();
350          label_Client->setText(m_client);
351          label_Pool->setText(fld.next());
352          label_FileSet->setText(fld.next());
353          label_SchedTime->setText(fld.next());
354          label_StartTime->setText(fld.next());
355          label_EndTime->setText(fld.next());
356          duration = fld.next();
357          /* 
358           * Note: if we have a negative duration, it is because the EndTime
359           *  is zero (i.e. the Job is still running).  We should use 
360           *  duration = StartTime - current_time
361           */
362          if (duration.left(1) == "-") {
363             duration = "0.0";
364          }
365          label_Duration->setText(duration);
366
367          label_JobBytes->setText(convertBytesSI(fld.next().toULongLong()));
368          label_JobFiles->setText(fld.next());
369          err = fld.next();
370          label_JobErrors->setText(err);
371
372          stat = fld.next();
373          if (stat == "T" && err.toInt() > 0) {
374             stat = "W";
375          }
376          if (stat == "R") {
377             pbDelete->setVisible(false);
378             pbCancel->setVisible(true);
379             grpRun->setVisible(true);
380             if (!m_timer) {
381                m_timer = new QTimer(this);
382                connect(m_timer, SIGNAL(timeout()), this, SLOT(populateAll()));
383                m_timer->start(30000);
384             }
385             updateRunInfo();
386          } else {
387             pbDelete->setVisible(true);
388             pbCancel->setVisible(false);
389             grpRun->setVisible(false);
390             if (m_timer) {
391                m_timer->stop();
392                delete m_timer;
393                m_timer = NULL;
394             }
395          }
396          label_JobStatus->setPixmap(QPixmap(":/images/" + stat + ".png"));
397          jobstatus_to_ascii_gui(stat[0].toAscii(), buf, sizeof(buf));
398          stat = buf;
399          label_JobStatus->setToolTip(stat);
400
401          chkbox_PurgedFiles->setCheckState(fld.next().toInt()?Qt::Checked:Qt::Unchecked);
402       }
403    }
404 }
405   
406 void Job::populateVolumes()
407 {
408
409    QString query = 
410       "SELECT DISTINCT VolumeName, InChanger, Slot "
411       "FROM Job JOIN JobMedia USING (JobId) JOIN Media USING (MediaId) "
412       "WHERE JobId=" + m_jobId + " ORDER BY VolumeName "; 
413    if (mainWin->m_sqlDebug) Pmsg1(0, "Query cmd : %s\n",query.toUtf8().data());
414          
415
416    QStringList results;
417    if (m_console->sql_cmd(query, results)) {
418       QString resultline;
419       QStringList fieldlist;
420       list_Volume->clear();
421       foreach (resultline, results) { // should have only one result
422          fieldlist = resultline.split("\t");
423          QStringListIterator fld(fieldlist);
424 //         QListWidgetItem(QIcon(":/images/inchanger" + fld.next() + ".png"), 
425 //                         fld.next(), list_Volume);
426          list_Volume->addItem(fld.next());
427       }
428    }
429 }
430
431 //QListWidgetItem ( const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type )