2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2009 Free Software Foundation Europe e.V.
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
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.
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
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.
31 #include "util/fmtwidgetitem.h"
32 #include "mediainfo/mediainfo.h"
35 Job::Job(QString &jobId, QTreeWidgetItem *parentTreeWidgetItem) : Pages()
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());
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)));
62 new runPage(label_Name->text(),
65 QString(""), // storage
67 label_FileSet->text());
70 void Job::showInfoVolume(QListWidgetItem *item)
72 QString s= item->text();
73 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
75 MediaInfo *m = new MediaInfo(pageSelectorTreeWidgetItem, s);
76 connect(m, SIGNAL(destroyed()), this, SLOT(populateTree()));
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; }
93 QString cmd("delete job jobid=");
95 consoleCommand(cmd, false);
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; }
106 QString cmd("cancel jobid=");
108 consoleCommand(cmd, false);
113 QFont font = textJobLog->font();
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());
123 textJobLog->setFont(font);
126 void Job::populateAll()
128 // Pmsg0(50, "populateAll()\n");
135 * Populate the text in the window
136 * TODO: Just append new text instead of clearing the window
138 void Job::populateText()
142 query = "SELECT Time, LogText FROM Log WHERE JobId='" + m_jobId + "' order by Time";
144 /* This could be a log item */
145 if (mainWin->m_sqlDebug) {
146 Pmsg1(000, "Log query cmd : %s\n", query.toUtf8().data());
150 if (m_console->sql_cmd(query, results)) {
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);
160 QString jobstr("JobId "); /* FIXME: should this be translated ? */
163 QString htmlbuf("<html><body><pre>");
165 /* Iterate through the lines of results. */
167 QStringList fieldlist;
170 foreach (QString resultline, results) {
171 fieldlist = resultline.split("\t");
173 if (fieldlist.size() < 2)
176 QString curTime = fieldlist[0].trimmed();
178 field = fieldlist[1].trimmed();
179 int colon = field.indexOf(":");
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) {
191 // htmlbuf += "<td>" + curTime + "</td>";
192 htmlbuf += "\n" + curSvc + " ";
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>";
204 /* non standard string, place as-is */
205 if (curTime == lastTime) {
210 // htmlbuf += "<td>" + curTime + "</td>";
211 htmlbuf += "\n" + field ;
214 } /* foreach resultline */
216 htmlbuf += "</pre></body></html>";
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);
224 } /* if results from query */
228 void Job::storeBwLimit(int val)
233 void Job::updateRunInfo()
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);
249 cmd = QString(".status client=\"" + m_client + "\" running");
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
255 * Processing file: /tmp/regress/build/po/de.po
260 * Job=backup.2010-12-21_09.28.17_03
266 QRegExp jobline("(JobId) (\\d+) Job ");
267 QRegExp itemline("([\\w /]+)[:=]\\s*(.+)");
268 QRegExp oldline("Files=([\\d,]+) Bytes=([\\d,]+) Bytes/sec=([\\d,]+) Errors=([\\d,]+)");
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) {
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()));
286 } else if (jobline.indexIn(line) >= 0) {
287 lst = jobline.capturedTexts();
290 } else if (itemline.indexIn(line) >= 0) {
291 lst = itemline.capturedTexts();
295 if (mainWin->m_miscDebug)
296 Pmsg1(0, "bad line=%s\n", line.toUtf8().data());
299 if (lst.count() < 2) {
300 if (mainWin->m_miscDebug)
301 Pmsg2(0, "bad line=%s count=%d\n", line.toUtf8().data(), lst.count());
303 if (lst[0] == "JobId") {
304 if (lst[1] == m_jobId) {
314 // } else if (lst[0] == "Job") {
315 // grpRun->setTitle(lst[1]);
318 // } else if (lst[0] == "VSS") {
320 // } else if (lst[0] == "Level") {
321 // Info->setText(lst[1]);
323 // } else if (lst[0] == "JobType") {
325 // } else if (lst[0] == "JobStarted") {
326 // Started->setText(lst[1]);
328 if (lst[0] == "Bwlimit") {
329 int val = lst[1].toInt();
331 chk_Bwlimit->setChecked(true);
332 spin_Bwlimit->setEnabled(true);
333 spin_Bwlimit->setValue(lst[1].toInt()/1024);
335 chk_Bwlimit->setEnabled(false);
336 spin_Bwlimit->setEnabled(false);
337 spin_Bwlimit->setValue(0);
340 } else if (lst[0] == "Errors") {
341 label_JobErrors->setText(lst[1]);
343 } else if (lst[0] == "Bytes/sec") {
344 label_Speed->setText(convertBytesSI(lst[1].toULongLong())+"/s");
346 } else if (lst[0] == "Files") {
347 label_JobFiles->setText(lst[1]);
349 } else if (lst[0] == "Bytes") {
350 label_JobBytes->setText(convertBytesSI(lst[1].toULongLong()));
352 } else if (lst[0] == "Files Examined") {
353 label_FilesExamined->setText(lst[1]);
355 } else if (lst[0] == "Processing file") {
356 label_CurrentFile->setText(lst[1]);
364 * Populate the text in the window
366 void Job::populateForm()
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;
379 if (m_console->sql_cmd(query, results)) {
380 QString resultline, duration;
381 QStringList fieldlist;
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());
389 label_Level->setText(job_level_to_str(fld.next()[0].toAscii()));
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();
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
404 if (duration.left(1) == "-") {
407 label_Duration->setText(duration);
409 label_JobBytes->setText(convertBytesSI(fld.next().toULongLong()));
410 label_JobFiles->setText(fld.next());
412 label_JobErrors->setText(err);
415 if (stat == "T" && err.toInt() > 0) {
419 pbDelete->setVisible(false);
420 pbCancel->setVisible(true);
421 grpRun->setVisible(true);
423 m_timer = new QTimer(this);
424 connect(m_timer, SIGNAL(timeout()), this, SLOT(populateAll()));
425 m_timer->start(30000);
429 pbDelete->setVisible(true);
430 pbCancel->setVisible(false);
431 grpRun->setVisible(false);
438 label_JobStatus->setPixmap(QPixmap(":/images/" + stat + ".png"));
439 jobstatus_to_ascii_gui(stat[0].toAscii(), buf, sizeof(buf));
441 label_JobStatus->setToolTip(stat);
443 chkbox_PurgedFiles->setCheckState(fld.next().toInt()?Qt::Checked:Qt::Unchecked);
448 void Job::populateVolumes()
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());
459 if (m_console->sql_cmd(query, results)) {
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());
473 //QListWidgetItem ( const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type )