2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2016 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
22 #include "util/fmtwidgetitem.h"
23 #include "mediainfo/mediainfo.h"
26 Job::Job(QString &jobId, QTreeWidgetItem *parentTreeWidgetItem) : Pages()
29 pgInitialize(tr("Job"), parentTreeWidgetItem);
30 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
31 thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/joblog.png")));
32 m_cursor = new QTextCursor(textJobLog->document());
39 connect(pbRefresh, SIGNAL(clicked()), this, SLOT(populateAll()));
40 connect(pbDelete, SIGNAL(clicked()), this, SLOT(deleteJob()));
41 connect(pbCancel, SIGNAL(clicked()), this, SLOT(cancelJob()));
42 connect(pbRun, SIGNAL(clicked()), this, SLOT(rerun()));
43 connect(list_Volume, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(showInfoVolume(QListWidgetItem *)));
44 connect(spin_Bwlimit, SIGNAL(valueChanged(int)), this, SLOT(storeBwLimit(int)));
53 new runPage(label_Name->text(),
56 QString(""), // storage
58 label_FileSet->text());
61 void Job::showInfoVolume(QListWidgetItem *item)
63 QString s= item->text();
64 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
66 MediaInfo *m = new MediaInfo(pageSelectorTreeWidgetItem, s);
67 connect(m, SIGNAL(destroyed()), this, SLOT(populateTree()));
72 if (QMessageBox::warning(this, "Bat",
73 tr("Are you sure you want to delete?? !!!.\n"
74 "This delete command is used to delete a Job record and all associated catalog"
75 " records that were created. This command operates only on the Catalog"
76 " database and has no effect on the actual data written to a Volume. This"
77 " command can be dangerous and we strongly recommend that you do not use"
78 " it unless you know what you are doing. The Job and all its associated"
79 " records (File and JobMedia) will be deleted from the catalog."
80 "Press OK to proceed with delete operation.?"),
81 QMessageBox::Ok | QMessageBox::Cancel)
82 == QMessageBox::Cancel) { return; }
84 QString cmd("delete job jobid=");
86 consoleCommand(cmd, false);
92 if (QMessageBox::warning(this, "Bat",
93 tr("Are you sure you want to cancel this job?"),
94 QMessageBox::Ok | QMessageBox::Cancel)
95 == QMessageBox::Cancel) { return; }
97 QString cmd("cancel jobid=");
99 consoleCommand(cmd, false);
104 QFont font = textJobLog->font();
107 m_console->getDirResName(dirname);
108 QSettings settings(dirname, "bat");
109 settings.beginGroup("Console");
110 font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
111 font.setPointSize(settings.value("consolePointSize", 10).toInt());
112 font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
114 textJobLog->setFont(font);
117 void Job::populateAll()
119 // Pmsg0(50, "populateAll()\n");
126 * Populate the text in the window
127 * TODO: Just append new text instead of clearing the window
129 void Job::populateText()
133 query = "SELECT Time, LogText FROM Log WHERE JobId='" + m_jobId + "' order by Time";
135 /* This could be a log item */
136 if (mainWin->m_sqlDebug) {
137 Pmsg1(000, "Log query cmd : %s\n", query.toUtf8().data());
141 if (m_console->sql_cmd(query, results)) {
143 if (!results.size()) {
144 QMessageBox::warning(this, tr("Bat"),
145 tr("There were no results!\n"
146 "It is possible you may need to add \"catalog = all\" "
147 "to the Messages resource for this job.\n"), QMessageBox::Ok);
151 QString jobstr("JobId "); /* FIXME: should this be translated ? */
154 QString htmlbuf("<html><body><pre>");
156 /* Iterate through the lines of results. */
158 QStringList fieldlist;
161 foreach (QString resultline, results) {
162 fieldlist = resultline.split("\t");
164 if (fieldlist.size() < 2)
167 QString curTime = fieldlist[0].trimmed();
169 field = fieldlist[1].trimmed();
170 int colon = field.indexOf(":");
172 /* string is like <service> <jobId xxxx>: ..."
173 * we split at ':' then remove the jobId xxxx string (always the same) */
174 QString curSvc(field.left(colon).replace(jobstr,"").trimmed());
175 if (curSvc == lastSvc && curTime == lastTime) {
182 // htmlbuf += "<td>" + curTime + "</td>";
183 htmlbuf += "\n" + curSvc + " ";
185 /* rest of string is marked as pre-formatted (here trimming should
186 * be avoided, to preserve original formatting) */
187 QString msg(field.mid(colon+2));
188 if (msg.startsWith( tr("Error:")) ) { /* FIXME: should really be translated ? */
189 /* error msg, use a specific class */
190 htmlbuf += "</pre><pre class=err>" + msg + "</pre><pre>";
195 /* non standard string, place as-is */
196 if (curTime == lastTime) {
201 // htmlbuf += "<td>" + curTime + "</td>";
202 htmlbuf += "\n" + field ;
205 } /* foreach resultline */
207 htmlbuf += "</pre></body></html>";
209 /* full text ready. Here a custom sheet is used to align columns */
210 QString logSheet(".err {color:#FF0000;}");
211 textJobLog->document()->setDefaultStyleSheet(logSheet);
212 textJobLog->document()->setHtml(htmlbuf);
213 textJobLog->moveCursor(QTextCursor::Start);
215 } /* if results from query */
219 void Job::storeBwLimit(int val)
224 void Job::updateRunInfo()
232 /* This doesn't seem like the right thing to do */
233 if (m_bwlimit >= 100) {
234 cmd = QString("setbandwidth limit=" + QString::number(m_bwlimit)
235 + " jobid=" + m_jobId);
236 m_console->dir_cmd(cmd, results);
242 cmd = QString(".status client=\"" + m_client + "\" running");
244 * JobId 5 Job backup.2010-12-21_09.28.17_03 is running.
245 * VSS Full Backup Job started: 21-Dec-10 09:28
246 * Files=4 Bytes=610,976 Bytes/sec=87,282 Errors=0
248 * Processing file: /tmp/regress/build/po/de.po
253 * Job=backup.2010-12-21_09.28.17_03
259 QRegExp jobline("(JobId) (\\d+) Job ");
260 QRegExp itemline("([\\w /]+)[:=]\\s*(.+)");
261 QRegExp filesline("Files: Examined=([\\d,]+) Backed up=([\\d,])");
262 QRegExp oldline("Files=([\\d,]+) Bytes=([\\d,]+) Bytes/sec=([\\d,]+) Errors=([\\d,]+)");
263 QRegExp restoreline("Files: Restored=([\\d,]+) Expected=([\\d,]+) Completed=([\\d,]+)%");
264 QRegExp restoreline2("Files Examined=([\\d,]+) Expected Files=([\\d,]+) Percent Complete=([\\d,]+)");
269 if (m_console->dir_cmd(cmd, results)) {
270 foreach (QString mline, results) {
271 foreach (QString line, mline.split("\n")) {
272 line = line.trimmed();
273 if (oldline.indexIn(line) >= 0) {
275 lst = oldline.capturedTexts();
276 label_JobErrors->setText(lst[4]);
277 label_Speed->setText(convertBytesSI(lst[3].replace(com, empty).toULongLong())+"/s");
278 label_JobFiles->setText(lst[1]);
279 label_JobBytes->setText(convertBytesSI(lst[2].replace(com, empty).toULongLong()));
283 } else if (filesline.indexIn(line) >= 0) {
285 lst = filesline.capturedTexts(); // Will also catch Backed up
286 label_FilesExamined->setText(lst[1]);
290 // TODO: Need to be fixed
291 // } else if (restoreline2.indexIn(line) >= 0) {
293 // lst = filesline.capturedTexts();
294 // label_FilesExamined->setText(lst[1]); // Can also handle Expected and Completed
298 } else if (jobline.indexIn(line) >= 0) {
299 lst = jobline.capturedTexts();
302 } else if (itemline.indexIn(line) >= 0) {
303 lst = itemline.capturedTexts();
307 if (mainWin->m_miscDebug)
308 Pmsg1(0, "bad line=%s\n", line.toUtf8().data());
311 if (lst.count() < 2) {
312 if (mainWin->m_miscDebug)
313 Pmsg2(0, "bad line=%s count=%d\n", line.toUtf8().data(), lst.count());
315 if (lst[0] == "JobId") {
316 if (lst[1] == m_jobId) {
326 // } else if (lst[0] == "Job") {
327 // grpRun->setTitle(lst[1]);
330 // } else if (lst[0] == "VSS") {
332 // } else if (lst[0] == "Level") {
333 // Info->setText(lst[1]);
335 // } else if (lst[0] == "JobType" || lst[0] == "Type") {
337 // } else if (lst[0] == "JobStarted" || lst[0] == "StartTime") {
338 // Started->setText(lst[1]);
341 if (lst[0] == "Bwlimit") {
342 int val = lst[1].toInt();
344 chk_Bwlimit->setChecked(true);
345 spin_Bwlimit->setEnabled(true);
346 spin_Bwlimit->setValue(lst[1].toInt()/1024);
348 chk_Bwlimit->setEnabled(false);
349 spin_Bwlimit->setEnabled(false);
350 spin_Bwlimit->setValue(0);
354 if (lst[0] == "Errors") {
355 label_JobErrors->setText(lst[1]);
357 } else if (lst[0] == "Bytes/sec") {
358 label_Speed->setText(convertBytesSI(lst[1].toULongLong())+"/s");
360 } else if (lst[0] == "Files" || lst[0] == "JobFiles") {
361 label_JobFiles->setText(lst[1]);
363 } else if (lst[0] == "Bytes" || lst[0] == "JobBytes") {
364 label_JobBytes->setText(convertBytesSI(lst[1].toULongLong()));
366 } else if (lst[0] == "Examined") {
367 label_FilesExamined->setText(lst[1]);
369 } else if (lst[0] == "Files Examined") {
370 label_FilesExamined->setText(lst[1]);
372 } else if (lst[0] == "Processing file") {
373 label_CurrentFile->setText(lst[1]);
381 * Populate the text in the window
383 void Job::populateForm()
388 "SELECT JobId, Job.Name, Level, Client.Name, Pool.Name, FileSet,"
389 "SchedTime, StartTime, EndTime, EndTime-StartTime AS Duration, "
390 "JobBytes, JobFiles, JobErrors, JobStatus, PurgedFiles "
391 "FROM Job JOIN Client USING (ClientId) "
392 "LEFT JOIN Pool ON (Job.PoolId = Pool.PoolId) "
393 "LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)"
394 "WHERE JobId=" + m_jobId;
396 if (m_console->sql_cmd(query, results)) {
397 QString resultline, duration;
398 QStringList fieldlist;
400 foreach (resultline, results) { // should have only one result
401 fieldlist = resultline.split("\t");
402 if (fieldlist.size() != 15) {
403 Pmsg1(000, "Unexpected line %s", resultline.toUtf8().data());
406 QStringListIterator fld(fieldlist);
407 label_JobId->setText(fld.next());
408 label_Name->setText(fld.next());
410 label_Level->setText(job_level_to_str(fld.next()[0].toLatin1()));
412 m_client = fld.next();
413 label_Client->setText(m_client);
414 label_Pool->setText(fld.next());
415 label_FileSet->setText(fld.next());
416 label_SchedTime->setText(fld.next());
417 label_StartTime->setText(fld.next());
418 label_EndTime->setText(fld.next());
419 duration = fld.next();
421 * Note: if we have a negative duration, it is because the EndTime
422 * is zero (i.e. the Job is still running). We should use
423 * duration = StartTime - current_time
425 if (duration.left(1) == "-") {
428 label_Duration->setText(duration);
430 label_JobBytes->setText(convertBytesSI(fld.next().toULongLong()));
431 label_JobFiles->setText(fld.next());
433 label_JobErrors->setText(err);
436 if (stat == "T" && err.toInt() > 0) {
440 pbDelete->setVisible(false);
441 pbCancel->setVisible(true);
442 grpRun->setVisible(true);
444 m_timer = new QTimer(this);
445 connect(m_timer, SIGNAL(timeout()), this, SLOT(populateAll()));
446 m_timer->start(30000);
450 pbDelete->setVisible(true);
451 pbCancel->setVisible(false);
452 grpRun->setVisible(false);
459 label_JobStatus->setPixmap(QPixmap(":/images/" + stat + ".png"));
460 jobstatus_to_ascii_gui(stat[0].toLatin1(), buf, sizeof(buf));
462 label_JobStatus->setToolTip(stat);
464 chkbox_PurgedFiles->setCheckState(fld.next().toInt()?Qt::Checked:Qt::Unchecked);
469 void Job::populateVolumes()
473 "SELECT DISTINCT VolumeName, InChanger, Slot "
474 "FROM Job JOIN JobMedia USING (JobId) JOIN Media USING (MediaId) "
475 "WHERE JobId=" + m_jobId + " ORDER BY VolumeName ";
476 if (mainWin->m_sqlDebug) Pmsg1(0, "Query cmd : %s\n",query.toUtf8().data());
480 if (m_console->sql_cmd(query, results)) {
482 QStringList fieldlist;
483 list_Volume->clear();
484 foreach (resultline, results) { // should have only one result
485 fieldlist = resultline.split("\t");
486 QStringListIterator fld(fieldlist);
487 // QListWidgetItem(QIcon(":/images/inchanger" + fld.next() + ".png"),
488 // fld.next(), list_Volume);
489 list_Volume->addItem(fld.next());
494 //QListWidgetItem ( const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type )