2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2007-2009 Free Software Foundation Europe e.V.
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.
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.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
23 #include "util/fmtwidgetitem.h"
24 #include "mediainfo/mediainfo.h"
27 Job::Job(QString &jobId, QTreeWidgetItem *parentTreeWidgetItem) : Pages()
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());
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)));
54 new runPage(label_Name->text(),
57 QString(""), // storage
59 label_FileSet->text());
62 void Job::showInfoVolume(QListWidgetItem *item)
64 QString s= item->text();
65 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
67 MediaInfo *m = new MediaInfo(pageSelectorTreeWidgetItem, s);
68 connect(m, SIGNAL(destroyed()), this, SLOT(populateTree()));
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; }
85 QString cmd("delete job jobid=");
87 consoleCommand(cmd, false);
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; }
98 QString cmd("cancel jobid=");
100 consoleCommand(cmd, false);
105 QFont font = textJobLog->font();
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());
115 textJobLog->setFont(font);
118 void Job::populateAll()
120 // Pmsg0(50, "populateAll()\n");
127 * Populate the text in the window
128 * TODO: Just append new text instead of clearing the window
130 void Job::populateText()
134 query = "SELECT Time, LogText FROM Log WHERE JobId='" + m_jobId + "' order by Time";
136 /* This could be a log item */
137 if (mainWin->m_sqlDebug) {
138 Pmsg1(000, "Log query cmd : %s\n", query.toUtf8().data());
142 if (m_console->sql_cmd(query, results)) {
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);
152 QString jobstr("JobId "); /* FIXME: should this be translated ? */
155 QString htmlbuf("<html><body><pre>");
157 /* Iterate through the lines of results. */
159 QStringList fieldlist;
162 foreach (QString resultline, results) {
163 fieldlist = resultline.split("\t");
165 if (fieldlist.size() < 2)
168 QString curTime = fieldlist[0].trimmed();
170 field = fieldlist[1].trimmed();
171 int colon = field.indexOf(":");
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) {
183 // htmlbuf += "<td>" + curTime + "</td>";
184 htmlbuf += "\n" + curSvc + " ";
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>";
196 /* non standard string, place as-is */
197 if (curTime == lastTime) {
202 // htmlbuf += "<td>" + curTime + "</td>";
203 htmlbuf += "\n" + field ;
206 } /* foreach resultline */
208 htmlbuf += "</pre></body></html>";
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);
216 } /* if results from query */
220 void Job::storeBwLimit(int val)
225 void Job::updateRunInfo()
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);
243 cmd = QString(".status client=\"" + m_client + "\" running");
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
249 * Processing file: /tmp/regress/build/po/de.po
254 * Job=backup.2010-12-21_09.28.17_03
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,]+)");
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) {
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()));
284 } else if (filesline.indexIn(line) >= 0) {
286 lst = filesline.capturedTexts(); // Will also catch Backed up
287 label_FilesExamined->setText(lst[1]);
291 // TODO: Need to be fixed
292 // } else if (restoreline2.indexIn(line) >= 0) {
294 // lst = filesline.capturedTexts();
295 // label_FilesExamined->setText(lst[1]); // Can also handle Expected and Completed
299 } else if (jobline.indexIn(line) >= 0) {
300 lst = jobline.capturedTexts();
303 } else if (itemline.indexIn(line) >= 0) {
304 lst = itemline.capturedTexts();
308 if (mainWin->m_miscDebug)
309 Pmsg1(0, "bad line=%s\n", line.toUtf8().data());
312 if (lst.count() < 2) {
313 if (mainWin->m_miscDebug)
314 Pmsg2(0, "bad line=%s count=%d\n", line.toUtf8().data(), lst.count());
316 if (lst[0] == "JobId") {
317 if (lst[1] == m_jobId) {
327 // } else if (lst[0] == "Job") {
328 // grpRun->setTitle(lst[1]);
331 // } else if (lst[0] == "VSS") {
333 // } else if (lst[0] == "Level") {
334 // Info->setText(lst[1]);
336 // } else if (lst[0] == "JobType" || lst[0] == "Type") {
338 // } else if (lst[0] == "JobStarted" || lst[0] == "StartTime") {
339 // Started->setText(lst[1]);
342 if (lst[0] == "Bwlimit") {
343 int val = lst[1].toInt();
345 chk_Bwlimit->setChecked(true);
346 spin_Bwlimit->setEnabled(true);
347 spin_Bwlimit->setValue(lst[1].toInt()/1024);
349 chk_Bwlimit->setEnabled(false);
350 spin_Bwlimit->setEnabled(false);
351 spin_Bwlimit->setValue(0);
355 if (lst[0] == "Errors") {
356 label_JobErrors->setText(lst[1]);
358 } else if (lst[0] == "Bytes/sec") {
359 label_Speed->setText(convertBytesSI(lst[1].toULongLong())+"/s");
361 } else if (lst[0] == "Files" || lst[0] == "JobFiles") {
362 label_JobFiles->setText(lst[1]);
364 } else if (lst[0] == "Bytes" || lst[0] == "JobBytes") {
365 label_JobBytes->setText(convertBytesSI(lst[1].toULongLong()));
367 } else if (lst[0] == "Examined") {
368 label_FilesExamined->setText(lst[1]);
370 } else if (lst[0] == "Files Examined") {
371 label_FilesExamined->setText(lst[1]);
373 } else if (lst[0] == "Processing file") {
374 label_CurrentFile->setText(lst[1]);
382 * Populate the text in the window
384 void Job::populateForm()
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;
397 if (m_console->sql_cmd(query, results)) {
398 QString resultline, duration;
399 QStringList fieldlist;
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());
407 QStringListIterator fld(fieldlist);
408 label_JobId->setText(fld.next());
409 label_Name->setText(fld.next());
411 label_Level->setText(job_level_to_str(fld.next()[0].toAscii()));
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();
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
426 if (duration.left(1) == "-") {
429 label_Duration->setText(duration);
431 label_JobBytes->setText(convertBytesSI(fld.next().toULongLong()));
432 label_JobFiles->setText(fld.next());
434 label_JobErrors->setText(err);
437 if (stat == "T" && err.toInt() > 0) {
441 pbDelete->setVisible(false);
442 pbCancel->setVisible(true);
443 grpRun->setVisible(true);
445 m_timer = new QTimer(this);
446 connect(m_timer, SIGNAL(timeout()), this, SLOT(populateAll()));
447 m_timer->start(30000);
451 pbDelete->setVisible(true);
452 pbCancel->setVisible(false);
453 grpRun->setVisible(false);
460 label_JobStatus->setPixmap(QPixmap(":/images/" + stat + ".png"));
461 jobstatus_to_ascii_gui(stat[0].toAscii(), buf, sizeof(buf));
463 label_JobStatus->setToolTip(stat);
465 chkbox_PurgedFiles->setCheckState(fld.next().toInt()?Qt::Checked:Qt::Unchecked);
470 void Job::populateVolumes()
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());
481 if (m_console->sql_cmd(query, results)) {
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());
495 //QListWidgetItem ( const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type )