]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/joblist/joblist.cpp
Cancel a running job from joblist.
[bacula/bacula] / bacula / src / qt-console / joblist / joblist.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2007 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 two of the GNU General Public
10    License as published by the Free Software Foundation plus additions
11    that are listed 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 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 John Walker.
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  *   Version $Id: joblist.h 4230 2007-02-21 20:07:37Z kerns $
30  *
31  *   Dirk Bartley, March 2007
32  */
33  
34 #include <QAbstractEventDispatcher>
35 #include <QTableWidgetItem>
36 #include "bat.h"
37 #include "joblist.h"
38 #include "restore.h"
39 #include "joblog/joblog.h"
40
41 /*
42  * Constructor for the class
43  */
44 JobList::JobList(QString &mediaName, QString &clientname,
45          QTreeWidgetItem *parentTreeWidgetItem)
46 {
47    setupUi(this);
48    m_name = "Clients";
49    m_mediaName = mediaName;
50    m_clientName = clientname;
51    pgInitialize(parentTreeWidgetItem);
52    m_resultCount = 0;
53    m_populated = false;
54    m_closeable = false;
55    if ((m_mediaName != "") || (m_clientName != "")) { m_closeable=true; }
56    m_checkCurrentWidget = true;
57    createConnections();
58
59    /* Set Defaults for check and spin for limits */
60    limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
61    limitSpinBox->setValue(mainWin->m_recordLimitVal);
62    daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
63    daysSpinBox->setValue(mainWin->m_daysLimitVal);
64 }
65
66 /*
67  * The Meat of the class.
68  * This function will populate the QTableWidget, mp_tablewidget, with
69  * QTableWidgetItems representing the results of a query for what jobs exist on
70  * the media name passed from the constructor stored in m_mediaName.
71  */
72 void JobList::populateTable()
73 {
74    QStringList results;
75    QString resultline;
76    QBrush blackBrush(Qt::black);
77
78    if (!m_console->preventInUseConnect())
79        return;
80
81    /* Can't do this in constructor because not neccesarily conected in constructor */
82    if (!m_populated) {
83       clientsComboBox->addItem("Any");
84       clientsComboBox->addItems(m_console->client_list);
85       int clientIndex = clientsComboBox->findText(m_clientName, Qt::MatchExactly);
86       if (clientIndex != -1)
87          clientsComboBox->setCurrentIndex(clientIndex);
88
89       QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
90       if (mainWin->m_sqlDebug) {
91          Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
92       }
93       QStringList results, volumeList;
94       if (m_console->sql_cmd(query, results)) {
95          QString field;
96          QStringList fieldlist;
97          /* Iterate through the lines of results. */
98          foreach (QString resultline, results) {
99             fieldlist = resultline.split("\t");
100             volumeList.append(fieldlist[0]);
101          } /* foreach resultline */
102       } /* if results from query */
103       volumeComboBox->addItem("Any");
104       volumeComboBox->addItems(volumeList);
105       int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
106       if (volumeIndex != -1) {
107          volumeComboBox->setCurrentIndex(volumeIndex);
108       }
109       jobComboBox->addItem("Any");
110       jobComboBox->addItems(m_console->job_list);
111       levelComboBox->addItem("Any");
112       levelComboBox->addItems( QStringList() << "F" << "D" << "I");
113       purgedComboBox->addItem("Any");
114       purgedComboBox->addItems( QStringList() << "0" << "1");
115       statusComboBox->addItem("Any");
116       fileSetComboBox->addItem("Any");
117       fileSetComboBox->addItems(m_console->fileset_list);
118       QString statusQuery("SELECT JobStatusLong FROM Status");
119       if (mainWin->m_sqlDebug) {
120          Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
121       }
122       QStringList statusResults, statusLongList;
123       if (m_console->sql_cmd(statusQuery, statusResults)) {
124          QString field;
125          QStringList fieldlist;
126          /* Iterate through the lines of results. */
127          foreach (QString resultline, statusResults) {
128             fieldlist = resultline.split("\t");
129             statusLongList.append(fieldlist[0]);
130          } /* foreach resultline */
131       } /* if results from statusquery */
132       statusComboBox->addItems(statusLongList);
133    }
134
135    /* Set up query */
136    QString query("");
137    int volumeIndex = volumeComboBox->currentIndex();
138    if (volumeIndex != -1)
139       m_mediaName = volumeComboBox->itemText(volumeIndex);
140    query += "SELECT DISTINCT Job.Jobid AS Id, Job.Name AS JobName, Client.Name AS Client,"
141             " Job.Starttime AS JobStart, Job.Type AS JobType,"
142             " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
143             " Job.JobBytes AS Bytes,"
144             " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
145             " Job.PurgedFiles AS Purged, FileSet.FileSet"
146             " FROM Job"
147             " LEFT OUTER JOIN Client ON (Client.ClientId=Job.ClientId)"
148             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId)"
149             " LEFT OUTER JOIN Status ON (Job.JobStatus=Status.JobStatus)"
150             " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId)"
151             " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId)";
152    QStringList conditions;
153    if (m_mediaName != "Any") {
154       conditions.append("Media.VolumeName='" + m_mediaName + "'");
155    }
156    int clientIndex = clientsComboBox->currentIndex();
157    if (clientIndex != -1)
158       m_clientName = clientsComboBox->itemText(clientIndex);
159    if (m_clientName != "Any") {
160       conditions.append("Client.Name='" + m_clientName + "'");
161    }
162    int jobIndex = jobComboBox->currentIndex();
163    if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != "Any")) {
164       conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
165    }
166    int levelIndex = levelComboBox->currentIndex();
167    if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
168       conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
169    }
170    int statusIndex = statusComboBox->currentIndex();
171    if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
172       conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
173    }
174    int purgedIndex = purgedComboBox->currentIndex();
175    if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "Any")) {
176       conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
177    }
178    int fileSetIndex = fileSetComboBox->currentIndex();
179    if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != "Any")) {
180       conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
181    }
182    /* If Limit check box For limit by days is checked  */
183    if (daysCheckBox->checkState() == Qt::Checked) {
184       QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
185       QString since = stamp.toString(Qt::ISODate);
186       conditions.append("Job.Starttime>'" + since + "'");
187    }
188    bool first = true;
189    foreach (QString condition, conditions) {
190       if (first) {
191          query += " WHERE " + condition;
192          first = false;
193       } else {
194          query += " AND " + condition;
195       }
196    }
197    /* Descending */
198    query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
199    /* If Limit check box for limit records returned is checked  */
200    if (limitCheckBox->checkState() == Qt::Checked) {
201       QString limit;
202       limit.setNum(limitSpinBox->value());
203       query += " LIMIT " + limit;
204    }
205
206    /* Set up the Header for the table */
207    QStringList headerlist = (QStringList()
208       << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type" 
209       << "Job Level" << "Job Files" << "Job Bytes" << "Job Status"  << "Purged" << "File Set" );
210    m_purgedIndex = headerlist.indexOf("Purged");
211    m_typeIndex = headerlist.indexOf("Job Type");
212    m_statusIndex = headerlist.indexOf("Job Status");
213
214    /* Initialize the QTableWidget */
215    m_checkCurrentWidget = false;
216    mp_tableWidget->clear();
217    m_checkCurrentWidget = true;
218    mp_tableWidget->setColumnCount(headerlist.size());
219    mp_tableWidget->setHorizontalHeaderLabels(headerlist);
220
221    if (mainWin->m_sqlDebug) {
222       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
223    }
224    if (m_console->sql_cmd(query, results)) {
225       m_resultCount = results.count();
226
227       QTableWidgetItem* p_tableitem;
228       QString field;
229       QStringList fieldlist;
230       mp_tableWidget->setRowCount(results.size());
231
232       int row = 0;
233       /* Iterate through the record returned from the query */
234       foreach (resultline, results) {
235          fieldlist = resultline.split("\t");
236          int column = 0;
237          bool m_statusIndexDone = false;
238          QString statusCode("");
239          /* Iterate through fields in the record */
240          foreach (field, fieldlist) {
241             field = field.trimmed();  /* strip leading & trailing spaces */
242             if ((column == m_statusIndex) && (!m_statusIndexDone)){
243                m_statusIndexDone = true;
244                statusCode = field;
245             } else {
246                p_tableitem = new QTableWidgetItem(field,1);
247                p_tableitem->setFlags(0);
248                p_tableitem->setForeground(blackBrush);
249                mp_tableWidget->setItem(row, column, p_tableitem);
250                if (column == m_statusIndex)
251                   setStatusColor(p_tableitem, statusCode);
252                column++;
253             }
254          }
255          row++;
256       }
257    } 
258    /* Resize the columns */
259    mp_tableWidget->resizeColumnsToContents();
260    mp_tableWidget->resizeRowsToContents();
261    mp_tableWidget->verticalHeader()->hide();
262    if ((m_mediaName != "Any") && (m_resultCount == 0)){
263       /* for context sensitive searches, let the user know if there were no
264        * results */
265       QMessageBox::warning(this, tr("Bat"),
266           tr("The Jobs query returned no results.\n"
267          "Press OK to continue?"), QMessageBox::Ok );
268    }
269 }
270
271 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
272 {
273    QString greenchars("TCR");
274    QString redchars("BEf");
275    QString yellowchars("eDAFSMmsjdctp");
276    if (greenchars.contains(field, Qt::CaseSensitive)) {
277       item->setBackground(Qt::green);
278    } else if (redchars.contains(field, Qt::CaseSensitive)) {
279       item->setBackground(Qt::red);
280    } else if (yellowchars.contains(field, Qt::CaseSensitive)){ 
281       item->setBackground(Qt::yellow);
282    }
283 }
284
285 /*
286  * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
287  * The tree has been populated.
288  */
289 void JobList::PgSeltreeWidgetClicked()
290 {
291    if (!m_populated) {
292       populateTable();
293       m_populated=true;
294    }
295 }
296
297 /*
298  *  Virtual function override of pages function which is called when this page
299  *  is visible on the stack
300  */
301 void JobList::currentStackItem()
302 {
303    populateTable();
304    if (!m_populated) {
305       m_contextActions.append(actionRefreshJobList);
306       m_populated=true;
307    }
308 }
309
310 /*
311  * Virtual Function to return the name for the medialist tree widget
312  */
313 void JobList::treeWidgetName(QString &desc)
314 {
315    if ((m_mediaName == "") && (m_clientName == "")) {
316       desc = "Jobs";
317    } else {
318       desc = "Jobs ";
319       if (m_mediaName != "" ) {
320          desc += "on Volume " + m_mediaName;
321       }
322       if (m_clientName != "" ) {
323          desc += "of Client " + m_clientName;
324       }
325    }
326 }
327
328 /*
329  * This functions much line tableItemChanged for trees like the page selector,
330  * but I will do much less here
331  */
332 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
333 {
334    if (m_checkCurrentWidget) {
335       int row = currentItem->row();
336       QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
337       m_currentJob = jobitem->text();
338
339       /* include purged action or not */
340       jobitem = mp_tableWidget->item(row, m_purgedIndex);
341       QString purged = jobitem->text();
342       mp_tableWidget->removeAction(actionPurgeFiles);
343       if (purged == "0") {
344          mp_tableWidget->addAction(actionPurgeFiles);
345       }
346       /* include restore from time and job action or not */
347       jobitem = mp_tableWidget->item(row, m_typeIndex);
348       QString type = jobitem->text();
349       mp_tableWidget->removeAction(actionRestoreFromJob);
350       mp_tableWidget->removeAction(actionRestoreFromTime);
351       if (type == "B") {
352          mp_tableWidget->addAction(actionRestoreFromJob);
353          mp_tableWidget->addAction(actionRestoreFromTime);
354       }
355       /* include cancel action or not */
356       jobitem = mp_tableWidget->item(row, m_statusIndex);
357       QString status = jobitem->text();
358       mp_tableWidget->removeAction(actionCancelJob);
359       if (status == "Running") {
360          mp_tableWidget->addAction(actionCancelJob);
361       }
362    }
363 }
364
365 /*
366  * Function to create connections for context sensitive menu for this and
367  * the page selector
368  */
369 void JobList::createConnections()
370 {
371    /* connect to the action specific to this pages class that shows up in the 
372     * page selector tree */
373    connect(actionRefreshJobList, SIGNAL(triggered()), this,
374                 SLOT(populateTable()));
375    connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
376    /* for the tableItemChanged to maintain m_currentJob */
377    connect(mp_tableWidget, SIGNAL(
378            currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
379            this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
380
381    /* Do what is required for the local context sensitive menu */
382
383
384    /* setContextMenuPolicy is required */
385    mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
386
387    /* Add Actions */
388    mp_tableWidget->addAction(actionRefreshJobList);
389    mp_tableWidget->addAction(actionLongListJob);
390    mp_tableWidget->addAction(actionListJobid);
391    mp_tableWidget->addAction(actionListFilesOnJob);
392    mp_tableWidget->addAction(actionListJobMedia);
393    mp_tableWidget->addAction(actionListVolumes);
394    mp_tableWidget->addAction(actionDeleteJob);
395    mp_tableWidget->addAction(actionPurgeFiles);
396    mp_tableWidget->addAction(actionRestoreFromJob);
397    mp_tableWidget->addAction(actionRestoreFromTime);
398    mp_tableWidget->addAction(actionShowLogForJob);
399
400    /* Make Connections */
401    connect(actionLongListJob, SIGNAL(triggered()), this,
402                 SLOT(consoleLongListJob()));
403    connect(actionListJobid, SIGNAL(triggered()), this,
404                 SLOT(consoleListJobid()));
405    connect(actionListFilesOnJob, SIGNAL(triggered()), this,
406                 SLOT(consoleListFilesOnJob()));
407    connect(actionListJobMedia, SIGNAL(triggered()), this,
408                 SLOT(consoleListJobMedia()));
409    connect(actionListVolumes, SIGNAL(triggered()), this,
410                 SLOT(consoleListVolumes()));
411    connect(actionDeleteJob, SIGNAL(triggered()), this,
412                 SLOT(consoleDeleteJob()));
413    connect(actionPurgeFiles, SIGNAL(triggered()), this,
414                 SLOT(consolePurgeFiles()));
415    connect(actionRestoreFromJob, SIGNAL(triggered()), this,
416                 SLOT(preRestoreFromJob()));
417    connect(actionRestoreFromTime, SIGNAL(triggered()), this,
418                 SLOT(preRestoreFromTime()));
419    connect(actionShowLogForJob, SIGNAL(triggered()), this,
420                 SLOT(showLogForJob()));
421    connect(actionCancelJob, SIGNAL(triggered()), this,
422                 SLOT(consoleCancelJob()));
423 }
424
425 /*
426  * Functions to respond to local context sensitive menu sending console commands
427  * If I could figure out how to make these one function passing a string, Yaaaaaa
428  */
429 void JobList::consoleLongListJob()
430 {
431    QString cmd("llist jobid=");
432    cmd += m_currentJob;
433    consoleCommand(cmd);
434 }
435 void JobList::consoleListJobid()
436 {
437    QString cmd("list jobid=");
438    cmd += m_currentJob;
439    consoleCommand(cmd);
440 }
441 void JobList::consoleListFilesOnJob()
442 {
443    QString cmd("list files jobid=");
444    cmd += m_currentJob;
445    consoleCommand(cmd);
446 }
447 void JobList::consoleListJobMedia()
448 {
449    QString cmd("list jobmedia jobid=");
450    cmd += m_currentJob;
451    consoleCommand(cmd);
452 }
453 void JobList::consoleListVolumes()
454 {
455    QString cmd("list volumes jobid=");
456    cmd += m_currentJob;
457    consoleCommand(cmd);
458 }
459 void JobList::consoleDeleteJob()
460 {
461    if (QMessageBox::warning(this, tr("Bat"),
462       tr("Are you sure you want to delete??  !!!.\n"
463 "This delete command is used to delete a Job record and all associated catalog"
464 " records that were created. This command operates only on the Catalog"
465 " database and has no effect on the actual data written to a Volume. This"
466 " command can be dangerous and we strongly recommend that you do not use"
467 " it unless you know what you are doing.  The Job and all its associated"
468 " records (File and JobMedia) will be deleted from the catalog."
469       "Press OK to proceed with delete operation.?"),
470       QMessageBox::Ok | QMessageBox::Cancel)
471       == QMessageBox::Cancel) { return; }
472
473    QString cmd("delete job jobid=");
474    cmd += m_currentJob;
475    consoleCommand(cmd);
476 }
477 void JobList::consolePurgeFiles()
478 {
479    if (QMessageBox::warning(this, tr("Bat"),
480       tr("Are you sure you want to purge ??  !!!.\n"
481 "The Purge command will delete associated Catalog database records from Jobs and"
482 " Volumes without considering the retention period. Purge  works only on the"
483 " Catalog database and does not affect data written to Volumes. This command can"
484 " be dangerous because you can delete catalog records associated with current"
485 " backups of files, and we recommend that you do not use it unless you know what"
486 " you are doing.\n"
487       "Press OK to proceed with the purge operation?"),
488       QMessageBox::Ok | QMessageBox::Cancel)
489       == QMessageBox::Cancel) { return; }
490
491    QString cmd("purge files jobid=");
492    cmd += m_currentJob;
493    consoleCommand(cmd);
494 }
495
496 /*
497  * Subroutine to call preRestore to restore from a select job
498  */
499 void JobList::preRestoreFromJob()
500 {
501    new prerestorePage(m_currentJob, R_JOBIDLIST);
502 }
503
504 /*
505  * Subroutine to call preRestore to restore from a select job
506  */
507 void JobList::preRestoreFromTime()
508 {
509    new prerestorePage(m_currentJob, R_JOBDATETIME);
510 }
511
512 /*
513  * Subroutine to call class to show the log in the database from that job
514  */
515 void JobList::showLogForJob()
516 {
517    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
518    new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
519 }
520
521 /*
522  * Cancel a running job
523  */
524 void JobList::consoleCancelJob()
525 {
526    QString cmd("cancel jobid=");
527    cmd += m_currentJob;
528    consoleCommand(cmd);
529 }