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